This commit is contained in:
lunaticbum 2025-09-05 18:02:27 +09:00
parent 2987825cb2
commit 4cb009d150
67 changed files with 2296 additions and 5413 deletions

View File

@ -89,7 +89,7 @@ class SecurityConfig(
"/spider/new**",
"/rank/**","/sudoku/**","/spider/**",
"/puzzle/play","/puzzle/2048","/puzzle/play/**","/puzzle/sudoku","/puzzle/spider",
"/css/**", "/js/**", "/images/**", "/webjars/**", "/assets/**").permitAll()
"/webfonts/**", "/css/**", "/js/**", "/images/**", "/webjars/**", "/assets/**").permitAll()
.anyRequest().authenticated()
}.formLogin { form ->
form.loginPage("/user/login.bs")

View File

@ -42,7 +42,7 @@ import java.util.*
@RestController
@RequestMapping("/blog")
class BlogController() {
class BlogController(private val commentService : CommentService) {
companion object {
val TEMPTOKEN = "TEMP_TOKEN_VIBUM"
}
@ -58,20 +58,20 @@ class BlogController() {
@Autowired
lateinit var logService: LogService
val WRITE_PERMISSION_KEY = "PERMISSION"
@GetMapping("write/{token}","write.bs")
fun writ(@PathVariable token : String? ) : ResultMV{
val vm = ResultMV("content/blog/write")
if (token.equals(TEMPTOKEN)) {
vm.modelMap.put(WRITE_PERMISSION_KEY,"OK")
vm.modelMap.put(EncTypeKey, EncType11)
vm.modelMap.put(ApiKeyWordKey,"WRITE")
vm.modelMap.put("title","회원이 들어는 구나~!!")
vm.modelMap.put("defaultTitle","무제(無題) (Untitled, ${SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date())})")
} else {
vm.modelMap.put(WRITE_PERMISSION_KEY,"NO")
}
return vm
}
// @GetMapping("write/{token}","write.bs")
// fun writ(@PathVariable token : String? ) : ResultMV{
// val vm = ResultMV("content/blog/write")
// if (token.equals(TEMPTOKEN)) {
// vm.modelMap.put(WRITE_PERMISSION_KEY,"OK")
// vm.modelMap.put(EncTypeKey, EncType11)
// vm.modelMap.put(ApiKeyWordKey,"WRITE")
// vm.modelMap.put("title","회원이 들어는 구나~!!")
// vm.modelMap.put("defaultTitle","무제(無題) (Untitled, ${SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date())})")
// } else {
// vm.modelMap.put(WRITE_PERMISSION_KEY,"NO")
// }
// return vm
// }
@PostMapping("post.bjx")
fun post(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity<ResponceResult> {
@ -234,18 +234,49 @@ class BlogController() {
return vm
}
@GetMapping("editor/{postId}")
fun editor(@PathVariable postId : String) : ResultMV{
// @GetMapping("editor/{postId}")
// fun editor(@PathVariable postId : String) : ResultMV{
// val vm = ResultMV("content/blog/editor")
// postManager.getPost(postId).block().apply {
// this?.title = URLDecoder.decode(this?.title)
// this?.content = URLDecoder.decode(this?.content)
// vm.modelMap.put("srcPost",this)
// }
// return vm
// }
@GetMapping(value = ["/edit", "/edit/{postId}"])
fun editPost(@PathVariable(required = false) postId: String?): ResultMV {
// 뷰는 'editor' 하나만 사용합니다.
val vm = ResultMV("content/blog/editor")
postManager.getPost(postId).block().apply {
this?.title = URLDecoder.decode(this?.title)
this?.content = URLDecoder.decode(this?.content)
vm.modelMap.put("srcPost",this)
if (postId == null) {
// 새 글 작성 (postId가 없는 경우)
// 새 Post 객체를 모델에 추가하여 th:object에서 오류가 나지 않도록 합니다.
val newPost = Post().apply {
title = "무제(無題) (${SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date())})"
content = "" // 내용은 비워둡니다.
}
vm.modelMap["srcPost"] = newPost
vm.modelMap["pageTitle"] = "새 글 작성" // 페이지 제목을 동적으로 설정
} else {
// 기존 글 수정 (postId가 있는 경우)
postManager.getPost(postId).block()?.apply {
this.title = URLDecoder.decode(this.title)
this.content = URLDecoder.decode(this.content)
vm.modelMap["srcPost"] = this
vm.modelMap["pageTitle"] = "글 수정" // 페이지 제목을 동적으로 설정
}
}
// 글쓰기 권한 및 기타 필요한 데이터를 모델에 추가합니다.
vm.modelMap.put(WRITE_PERMISSION_KEY,"OK")
vm.modelMap.put(EncTypeKey, EncType11)
vm.modelMap.put(ApiKeyWordKey,"WRITE")
return vm
}
@GetMapping("posts")
fun posts(pageable: Pageable) : ResultMV{
val vm = ResultMV("content/blog/posts")
@ -338,6 +369,31 @@ class BlogController() {
return vm
}
@GetMapping("categories.bjx")
fun getCategories(): Mono<ResponseEntity<TagResult>> {
val resultCode = 0
val resultMsg = "Success"
// Replace with your actual database query for categories
val categories = listOf("Technology", "Travel", "Food", "Lifestyle")
return Mono.just(ResponseEntity.ok().body(TagResult().apply {
this.resultCode = resultCode
this.resultMsg = resultMsg
this.tags = categories
}))
}
@GetMapping("hashtags.bjx")
fun getHashtags(): Mono<ResponseEntity<TagResult>> {
val resultCode = 0
val resultMsg = "Success"
// Replace with your actual database query for hashtags
val hashtags = listOf("kotlin", "spring", "travelgram", "foodie", "blogging")
return Mono.just(ResponseEntity.ok().body(TagResult().apply {
this.resultCode = resultCode
this.resultMsg = resultMsg
this.tags = hashtags
}))
}
@Value("\${image.upload.path}")
private val uploadPath: String? = null
@ -424,5 +480,86 @@ class BlogController() {
this.thumbnailName = "${uuid}_thumbnail.$extension"
})
}
// In BlogController.kt
// Add these new functions to your BlogController class
@GetMapping("posts/{postId}/comments.bjx")
fun getCommentsForPost(@PathVariable postId: String): Mono<ResponseEntity<CommentsResult>> {
val resultCode = 0
val resultMsg = "Success"
return commentService.getCommentsForPost(postId)
.collectList()
.map { commentsList ->
ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(CommentsResult().apply {
this.resultCode = resultCode
this.resultMsg = resultMsg
this.comments = commentsList
})
}
.onErrorResume {
Mono.just(ResponseEntity.status(500).body(CommentsResult().apply {
this.resultCode = 500
this.resultMsg = "Error fetching comments"
this.comments = emptyList()
}))
}
}
@PostMapping("posts/{postId}/comments.bjx")
fun addComment(@PathVariable postId: String, @RequestBody jsonString: String): Mono<ResponseEntity<ResponceResult>> {
try {
val decodedBytes: ByteArray = Base64.getDecoder().decode(jsonString)
val requestData = String(decodedBytes)
val comment = Gson().fromJson(requestData, Comment::class.java).apply {
this.postId = postId
this.writeTime = System.currentTimeMillis()
}
return commentService.addComment(comment)
.map {
ResponseEntity.ok().body(ResponceResult().apply {
resultCode = 0
resultMsg = "Comment submitted successfully"
})
}
.onErrorResume { e ->
Mono.just(ResponseEntity.status(500).body(ResponceResult().apply {
resultCode = 500
resultMsg = "Error submitting comment: ${e.message}"
}))
}
} catch (e: Exception) {
return Mono.just(ResponseEntity.status(400).body(ResponceResult().apply {
resultCode = 400
resultMsg = "Invalid request data"
}))
}
}
@GetMapping("comments/{commentId}/replies.bjx")
fun getRepliesForComment(@PathVariable commentId: String): Mono<ResponseEntity<CommentsResult>> {
val resultCode = 0
val resultMsg = "Success"
return commentService.getRepliesForComment(commentId)
.collectList()
.map { repliesList ->
ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(CommentsResult().apply {
this.resultCode = resultCode
this.resultMsg = resultMsg
this.comments = repliesList
})
}
.onErrorResume {
Mono.just(ResponseEntity.status(500).body(CommentsResult().apply {
this.resultCode = 500
this.resultMsg = "Error fetching replies"
this.comments = emptyList()
}))
}
}
}

View File

@ -33,7 +33,6 @@ class SpiderController(private val spiderService: SpiderService,) {
return spiderService.updateGame(game)
}
// 랭킹 등록 엔드포인트
@PostMapping("/register")
fun registerRank(@RequestBody rank: SpiderRank): Mono<ResponseEntity<SpiderRank>> {
@ -55,4 +54,11 @@ class SpiderController(private val spiderService: SpiderService,) {
val gameId = request["gameId"] ?: return Mono.error(IllegalArgumentException("Game ID is required."))
return spiderService.dealCardsFromStock(gameId)
}
// 실행 취소 엔드포인트 추가
@PostMapping("/undo")
fun undo(@RequestBody request: Map<String, String>): Mono<SpiderGame> {
val gameId = request["gameId"] ?: return Mono.error(IllegalArgumentException("Game ID is required."))
return spiderService.undoGame(gameId)
}
}

View File

@ -74,6 +74,27 @@ class Comment {
var writeTime: Long? = null
var mentions: List<String>? = null // 언급된 유저 아이디(선택)
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class TagResult {
var resultCode: Int = 0
var resultMsg: String = ""
var tags: List<String>? = null
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class CommentsResult {
var resultCode: Int = 0
var resultMsg: String = ""
var comments: List<Comment>? = null
}
@Repository
interface CommentRepository : ReactiveMongoRepository<Comment, String> {
fun findByPostIdAndParentIdIsNullOrderByWriteTimeAsc(postId: String): Flux<Comment> // 최상위 댓글
@ -82,7 +103,9 @@ interface CommentRepository : ReactiveMongoRepository<Comment, String> {
@Service
class CommentService(private val commentRepository: CommentRepository) {
fun getRepliesForComment(parentId: String): Flux<Comment> {
return commentRepository.findByParentIdOrderByWriteTimeAsc(parentId)
}
fun addComment(comment: Comment): Mono<Comment> {
// 예시: 부모 댓글 존재 여부/권한 검증 등 비즈니스 로직 처리
return commentRepository.save(comment)

View File

@ -1,3 +1,5 @@
// kr.lunaticbum.back.lun.model.Spider.kt
package kr.lunaticbum.back.lun.model
import org.springframework.data.annotation.Id
@ -17,6 +19,8 @@ data class SpiderGame(
val foundation: List<List<SpiderCard>>,
val moves: Int,
val isCompleted: Boolean,
val undoCount: Int = 0, // 실행 취소 횟수: 최대 5회 제한
val undoHistory: List<SpiderGameHistory> = emptyList(), // 게임 상태 히스토리 저장
val timestamp: Long = System.currentTimeMillis()
)
@ -26,19 +30,34 @@ data class SpiderCard(
var isFaceUp: Boolean,
)
// 게임 상태 히스토리를 저장할 데이터 클래스
data class SpiderGameHistory(
val tableau: List<List<SpiderCard>>,
val stock: List<SpiderCard>,
val foundation: List<List<SpiderCard>>,
val moves: Int
)
interface SpiderGameRepository : ReactiveMongoRepository<SpiderGame, String> {
override fun findById(id: String): Mono<SpiderGame>
}
@Service
class SpiderService(private val spiderGameRepository: SpiderGameRepository,
private val spiderRankRepository: SpiderRankRepository ) {
class SpiderService(
private val spiderGameRepository: SpiderGameRepository,
private val spiderRankRepository: SpiderRankRepository
) {
// 덱 생성, 카드 분배 등 기존 로직은 그대로 유지
private fun createDeck(numSuits: Int): List<SpiderCard> {
val allSuits = listOf("spade", "heart", "club", "diamond")
val suits = allSuits.take(numSuits)
val setsPerSuit = when (numSuits) {
1 -> 8 // 13 ranks * 1 suit * 8 sets = 104 cards
2 -> 4 // 13 ranks * 2 suits * 4 sets = 104 cards
4 -> 2 // 13 ranks * 4 suits * 2 sets = 104 cards
else -> throw IllegalArgumentException("Invalid number of suits: $numSuits")
}
val deck = mutableListOf<SpiderCard>()
val setsPerSuit = 8 / numSuits
repeat(setsPerSuit) {
for (suit in suits) {
for (rank in 1..13) {
@ -51,24 +70,31 @@ class SpiderService(private val spiderGameRepository: SpiderGameRepository,
private fun dealCards(shuffledCards: List<SpiderCard>, numCards: String): Pair<List<List<SpiderCard>>, List<SpiderCard>> {
val initialCards = numCards.split(",").map { it.trim().toInt() }
val cardsPerStack = listOf(
initialCards[0], initialCards[0], initialCards[0], initialCards[0],
initialCards[1], initialCards[1], initialCards[1], initialCards[1], initialCards[1], initialCards[1]
)
val cards = shuffledCards.toMutableList()
val tableau = mutableListOf<MutableList<SpiderCard>>()
cardsPerStack.forEach { num ->
val stack = mutableListOf<SpiderCard>()
repeat(num) {
val card = cards.removeFirst()
card.isFaceUp = (it == num - 1)
stack.add(card)
}
tableau.add(stack)
val cardsPerStack = List(10) { index ->
if (index < 4) initialCards[0] else initialCards[1]
}
return Pair(tableau, cards)
val cardsToDeal = shuffledCards.toMutableList()
val tableau = MutableList(10) { mutableListOf<SpiderCard>() }
// 각 스택에 초기 카드를 분배합니다.
cardsPerStack.forEachIndexed { stackIndex, count ->
repeat(count) {
if (cardsToDeal.isNotEmpty()) {
val card = cardsToDeal.removeFirst()
tableau[stackIndex].add(card)
}
}
}
// 각 스택의 맨 위 카드를 앞면으로 설정합니다.
tableau.forEach { stack ->
if (stack.isNotEmpty()) {
stack.last().isFaceUp = true
}
}
return Pair(tableau, cardsToDeal)
}
fun newGame(numSuits: Int, numCards: String): Mono<SpiderGame> {
@ -82,7 +108,9 @@ class SpiderService(private val spiderGameRepository: SpiderGameRepository,
stock = stock,
foundation = emptyList(),
moves = 0,
isCompleted = false
isCompleted = false,
undoCount = 0,
undoHistory = emptyList()
)
return spiderGameRepository.save(initialGame)
}
@ -91,29 +119,49 @@ class SpiderService(private val spiderGameRepository: SpiderGameRepository,
return spiderGameRepository.findById(id)
}
// updateGame 메서드: 게임 상태를 업데이트하기 전에 히스토리를 저장합니다.
fun updateGame(game: SpiderGame): Mono<SpiderGame> {
return spiderGameRepository.save(game)
val historyToSave = SpiderGameHistory(
tableau = game.tableau,
stock = game.stock,
foundation = game.foundation,
moves = game.moves
)
// 기존 히스토리에 현재 상태를 추가하고, 최대 5개까지만 유지합니다.
val updatedHistory = (game.undoHistory + historyToSave).takeLast(5)
val updatedGame = game.copy(undoHistory = updatedHistory)
return spiderGameRepository.save(updatedGame)
}
// dealCardsFromStock 메서드: 카드 분배 전에 히스토리를 저장합니다.
fun dealCardsFromStock(gameId: String): Mono<SpiderGame> {
return spiderGameRepository.findById(gameId)
.flatMap { game ->
val stockCards = game.stock.toMutableList()
if (stockCards.size >= 10) {
val cardsToDeal = stockCards.take(10)
// 현재 게임 상태를 히스토리에 저장
val historyToSave = SpiderGameHistory(
tableau = game.tableau,
stock = game.stock,
foundation = game.foundation,
moves = game.moves
)
val updatedHistory = (game.undoHistory + historyToSave).takeLast(5)
val updatedTableau = game.tableau.toMutableList()
val remainingStock = stockCards.drop(10)
updatedTableau.forEachIndexed { index, stack ->
val cardToDeal = cardsToDeal[index]
cardToDeal.isFaceUp = true
val cardToDeal = stockCards[index]
cardToDeal.isFaceUp = true // 카드를 추가할 때 앞면으로 뒤집기
(stack as MutableList).add(cardToDeal)
}
val updatedGame = game.copy(
tableau = updatedTableau,
stock = remainingStock,
moves = game.moves + 1
moves = game.moves + 1,
undoHistory = updatedHistory // 히스토리 업데이트
)
spiderGameRepository.save(updatedGame)
} else {
@ -122,12 +170,34 @@ class SpiderService(private val spiderGameRepository: SpiderGameRepository,
}
}
// 🔴 추가: 랭킹 등록 함수
// undoGame 메서드: 히스토리에서 마지막 상태를 불러와 게임을 되돌립니다.
fun undoGame(gameId: String): Mono<SpiderGame> {
return spiderGameRepository.findById(gameId)
.flatMap { game ->
if (game.undoHistory.isNotEmpty() && game.undoCount < 5) {
val lastHistory = game.undoHistory.last()
val remainingHistory = game.undoHistory.dropLast(1)
val updatedGame = game.copy(
tableau = lastHistory.tableau,
stock = lastHistory.stock,
foundation = lastHistory.foundation,
moves = lastHistory.moves,
undoCount = game.undoCount + 1, // 실행 취소 횟수 증가
undoHistory = remainingHistory // 마지막 히스토리는 제거
)
spiderGameRepository.save(updatedGame)
} else {
Mono.error(IllegalArgumentException("Cannot undo. No more history or undo limit reached."))
}
}
}
// 랭킹 관련 기존 메서드들은 그대로 유지
fun registerRank(rank: SpiderRank): Mono<SpiderRank> {
return spiderRankRepository.save(rank)
}
// 🔴 추가: 랭킹 조회 함수
fun getRanksByGameId(gameId: String): Flux<SpiderRank> {
return spiderRankRepository.findByGameIdOrderByMovesAscCompletionTimeAsc(gameId)
}

View File

@ -1,587 +0,0 @@
(function($) {
/**
* Generate an indented list of links from a nav. Meant for use with panel().
* @return {jQuery} jQuery object.
*/
$.fn.navList = function() {
var $this = $(this);
$a = $this.find('a'),
b = [];
$a.each(function() {
var $this = $(this),
indent = Math.max(0, $this.parents('li').length - 1),
href = $this.attr('href'),
target = $this.attr('target');
b.push(
'<a ' +
'class="link depth-' + indent + '"' +
( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
'>' +
'<span class="indent-' + indent + '"></span>' +
$this.text() +
'</a>'
);
});
return b.join('');
};
/**
* Panel-ify an element.
* @param {object} userConfig User config.
* @return {jQuery} jQuery object.
*/
$.fn.panel = function(userConfig) {
// No elements?
if (this.length == 0)
return $this;
// Multiple elements?
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i]).panel(userConfig);
return $this;
}
// Vars.
var $this = $(this),
$body = $('body'),
$window = $(window),
id = $this.attr('id'),
config;
// Config.
config = $.extend({
// Delay.
delay: 0,
// Hide panel on link click.
hideOnClick: false,
// Hide panel on escape keypress.
hideOnEscape: false,
// Hide panel on swipe.
hideOnSwipe: false,
// Reset scroll position on hide.
resetScroll: false,
// Reset forms on hide.
resetForms: false,
// Side of viewport the panel will appear.
side: null,
// Target element for "class".
target: $this,
// Class to toggle.
visibleClass: 'visible'
}, userConfig);
// Expand "target" if it's not a jQuery object already.
if (typeof config.target != 'jQuery')
config.target = $(config.target);
// Panel.
// Methods.
$this._hide = function(event) {
// Already hidden? Bail.
if (!config.target.hasClass(config.visibleClass))
return;
// If an event was provided, cancel it.
if (event) {
event.preventDefault();
event.stopPropagation();
}
// Hide.
config.target.removeClass(config.visibleClass);
// Post-hide stuff.
window.setTimeout(function() {
// Reset scroll position.
if (config.resetScroll)
$this.scrollTop(0);
// Reset forms.
if (config.resetForms)
$this.find('form').each(function() {
this.reset();
});
}, config.delay);
};
// Vendor fixes.
$this
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
.css('-webkit-overflow-scrolling', 'touch');
// Hide on click.
if (config.hideOnClick) {
$this.find('a')
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
$this
.on('click', 'a', function(event) {
var $a = $(this),
href = $a.attr('href'),
target = $a.attr('target');
if (!href || href == '#' || href == '' || href == '#' + id)
return;
// Cancel original event.
event.preventDefault();
event.stopPropagation();
// Hide panel.
$this._hide();
// Redirect to href.
window.setTimeout(function() {
if (target == '_blank')
window.open(href);
else
window.location.href = href;
}, config.delay + 10);
});
}
// Event: Touch stuff.
$this.on('touchstart', function(event) {
$this.touchPosX = event.originalEvent.touches[0].pageX;
$this.touchPosY = event.originalEvent.touches[0].pageY;
})
$this.on('touchmove', function(event) {
if ($this.touchPosX === null
|| $this.touchPosY === null)
return;
var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
th = $this.outerHeight(),
ts = ($this.get(0).scrollHeight - $this.scrollTop());
// Hide on swipe?
if (config.hideOnSwipe) {
var result = false,
boundary = 20,
delta = 50;
switch (config.side) {
case 'left':
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
break;
case 'right':
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
break;
case 'top':
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
break;
case 'bottom':
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
break;
default:
break;
}
if (result) {
$this.touchPosX = null;
$this.touchPosY = null;
$this._hide();
return false;
}
}
// Prevent vertical scrolling past the top or bottom.
if (($this.scrollTop() < 0 && diffY < 0)
|| (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
event.preventDefault();
event.stopPropagation();
}
});
// Event: Prevent certain events inside the panel from bubbling.
$this.on('click touchend touchstart touchmove', function(event) {
event.stopPropagation();
});
// Event: Hide panel if a child anchor tag pointing to its ID is clicked.
$this.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.removeClass(config.visibleClass);
});
// Body.
// Event: Hide panel on body click/tap.
$body.on('click touchend', function(event) {
$this._hide(event);
});
// Event: Toggle.
$body.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.toggleClass(config.visibleClass);
});
// Window.
// Event: Hide on ESC.
if (config.hideOnEscape)
$window.on('keydown', function(event) {
if (event.keyCode == 27)
$this._hide(event);
});
return $this;
};
/**
* Apply "placeholder" attribute polyfill to one or more forms.
* @return {jQuery} jQuery object.
*/
$.fn.placeholder = function() {
// Browser natively supports placeholders? Bail.
if (typeof (document.createElement('input')).placeholder != 'undefined')
return $(this);
// No elements?
if (this.length == 0)
return $this;
// Multiple elements?
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i]).placeholder();
return $this;
}
// Vars.
var $this = $(this);
// Text, TextArea.
$this.find('input[type=text],textarea')
.each(function() {
var i = $(this);
if (i.val() == ''
|| i.val() == i.attr('placeholder'))
i
.addClass('polyfill-placeholder')
.val(i.attr('placeholder'));
})
.on('blur', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
return;
if (i.val() == '')
i
.addClass('polyfill-placeholder')
.val(i.attr('placeholder'));
})
.on('focus', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
return;
if (i.val() == i.attr('placeholder'))
i
.removeClass('polyfill-placeholder')
.val('');
});
// Password.
$this.find('input[type=password]')
.each(function() {
var i = $(this);
var x = $(
$('<div>')
.append(i.clone())
.remove()
.html()
.replace(/type="password"/i, 'type="text"')
.replace(/type=password/i, 'type=text')
);
if (i.attr('id') != '')
x.attr('id', i.attr('id') + '-polyfill-field');
if (i.attr('name') != '')
x.attr('name', i.attr('name') + '-polyfill-field');
x.addClass('polyfill-placeholder')
.val(x.attr('placeholder')).insertAfter(i);
if (i.val() == '')
i.hide();
else
x.hide();
i
.on('blur', function(event) {
event.preventDefault();
var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') {
i.hide();
x.show();
}
});
x
.on('focus', function(event) {
event.preventDefault();
var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
x.hide();
i
.show()
.focus();
})
.on('keypress', function(event) {
event.preventDefault();
x.val('');
});
});
// Events.
$this
.on('submit', function() {
$this.find('input[type=text],input[type=password],textarea')
.each(function(event) {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
i.attr('name', '');
if (i.val() == i.attr('placeholder')) {
i.removeClass('polyfill-placeholder');
i.val('');
}
});
})
.on('reset', function(event) {
event.preventDefault();
$this.find('select')
.val($('option:first').val());
$this.find('input,textarea')
.each(function() {
var i = $(this),
x;
i.removeClass('polyfill-placeholder');
switch (this.type) {
case 'submit':
case 'reset':
break;
case 'password':
i.val(i.attr('defaultValue'));
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') {
i.hide();
x.show();
}
else {
i.show();
x.hide();
}
break;
case 'checkbox':
case 'radio':
i.attr('checked', i.attr('defaultValue'));
break;
case 'text':
case 'textarea':
i.val(i.attr('defaultValue'));
if (i.val() == '') {
i.addClass('polyfill-placeholder');
i.val(i.attr('placeholder'));
}
break;
default:
i.val(i.attr('defaultValue'));
break;
}
});
});
return $this;
};
/**
* Moves elements to/from the first positions of their respective parents.
* @param {jQuery} $elements Elements (or selector) to move.
* @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
*/
$.prioritize = function($elements, condition) {
var key = '__prioritize';
// Expand $elements if it's not already a jQuery object.
if (typeof $elements != 'jQuery')
$elements = $($elements);
// Step through elements.
$elements.each(function() {
var $e = $(this), $p,
$parent = $e.parent();
// No parent? Bail.
if ($parent.length == 0)
return;
// Not moved? Move it.
if (!$e.data(key)) {
// Condition is false? Bail.
if (!condition)
return;
// Get placeholder (which will serve as our point of reference for when this element needs to move back).
$p = $e.prev();
// Couldn't find anything? Means this element's already at the top, so bail.
if ($p.length == 0)
return;
// Move element to top of parent.
$e.prependTo($parent);
// Mark element as moved.
$e.data(key, $p);
}
// Moved already?
else {
// Condition is true? Bail.
if (condition)
return;
$p = $e.data(key);
// Move element back to its original location (using our placeholder).
$e.insertAfter($p);
// Unmark element as moved.
$e.removeData(key);
}
});
};
})(jQuery);

View File

@ -1,223 +0,0 @@
// breakpoints.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Breakpoints.
/// @var {list}
$breakpoints: () !global;
// Mixins.
/// Sets breakpoints.
/// @param {map} $x Breakpoints.
@mixin breakpoints($x: ()) {
$breakpoints: $x !global;
}
/// Wraps @content in a @media block targeting a specific orientation.
/// @param {string} $orientation Orientation.
@mixin orientation($orientation) {
@media screen and (orientation: #{$orientation}) {
@content;
}
}
/// Wraps @content in a @media block using a given query.
/// @param {string} $query Query.
@mixin breakpoint($query: null) {
$breakpoint: null;
$op: null;
$media: null;
// Determine operator, breakpoint.
// Greater than or equal.
@if (str-slice($query, 0, 2) == '>=') {
$op: 'gte';
$breakpoint: str-slice($query, 3);
}
// Less than or equal.
@elseif (str-slice($query, 0, 2) == '<=') {
$op: 'lte';
$breakpoint: str-slice($query, 3);
}
// Greater than.
@elseif (str-slice($query, 0, 1) == '>') {
$op: 'gt';
$breakpoint: str-slice($query, 2);
}
// Less than.
@elseif (str-slice($query, 0, 1) == '<') {
$op: 'lt';
$breakpoint: str-slice($query, 2);
}
// Not.
@elseif (str-slice($query, 0, 1) == '!') {
$op: 'not';
$breakpoint: str-slice($query, 2);
}
// Equal.
@else {
$op: 'eq';
$breakpoint: $query;
}
// Build media.
@if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
$a: map-get($breakpoints, $breakpoint);
// Range.
@if (type-of($a) == 'list') {
$x: nth($a, 1);
$y: nth($a, 2);
// Max only.
@if ($x == null) {
// Greater than or equal (>= 0 / anything)
@if ($op == 'gte') {
$media: 'screen';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< 0 / invalid)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: -1px)';
}
// Not (> y)
@elseif ($op == 'not') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (<= y)
@else {
$media: 'screen and (max-width: ' + $y + ')';
}
}
// Min only.
@else if ($y == null) {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= inf / anything)
@elseif ($op == 'lte') {
$media: 'screen';
}
// Greater than (> inf / invalid)
@elseif ($op == 'gt') {
$media: 'screen and (max-width: -1px)';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Equal (>= x)
@else {
$media: 'screen and (min-width: ' + $x + ')';
}
}
// Min and max.
@else {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x and > y)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (>= x and <= y)
@else {
$media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
}
}
}
// String.
@else {
// Missing a media type? Prefix with "screen".
@if (str-slice($a, 0, 1) == '(') {
$media: 'screen and ' + $a;
}
// Otherwise, use as-is.
@else {
$media: $a;
}
}
}
// Output.
@media #{$media} {
@content;
}
}

View File

@ -1,90 +0,0 @@
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Gets a value from a map.
/// @author Hugo Giraudel
/// @param {map} $map Map.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function val($map, $keys...) {
@if nth($keys, 1) == null {
$keys: remove-nth($keys, 1);
}
@each $key in $keys {
$map: map-get($map, $key);
}
@return $map;
}
/// Gets a duration value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _duration($keys...) {
@return val($duration, $keys...);
}
/// Gets a font value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _font($keys...) {
@return val($font, $keys...);
}
/// Gets a misc value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _misc($keys...) {
@return val($misc, $keys...);
}
/// Gets a palette value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _palette($keys...) {
@return val($palette, $keys...);
}
/// Gets a size value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _size($keys...) {
@return val($size, $keys...);
}

View File

@ -1,149 +0,0 @@
// html-grid.scss v1.0 | @ajlkn | MIT licensed */
// Mixins.
/// Initializes the current element as an HTML grid.
/// @param {mixed} $gutters Gutters (either a single number to set both column/row gutters, or a list to set them individually).
/// @param {mixed} $suffix Column class suffix (optional; either a single suffix or a list).
@mixin html-grid($gutters: 1.5em, $suffix: '') {
// Initialize.
$cols: 12;
$multipliers: 0, 0.25, 0.5, 1, 1.50, 2.00;
$unit: 100% / $cols;
// Suffixes.
$suffixes: null;
@if (type-of($suffix) == 'list') {
$suffixes: $suffix;
}
@else {
$suffixes: ($suffix);
}
// Gutters.
$guttersCols: null;
$guttersRows: null;
@if (type-of($gutters) == 'list') {
$guttersCols: nth($gutters, 1);
$guttersRows: nth($gutters, 2);
}
@else {
$guttersCols: $gutters;
$guttersRows: 0;
}
// Row.
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
align-items: stretch;
// Columns.
> * {
box-sizing: border-box;
}
// Gutters.
&.gtr-uniform {
> * {
> :last-child {
margin-bottom: 0;
}
}
}
// Alignment.
&.aln-left {
justify-content: flex-start;
}
&.aln-center {
justify-content: center;
}
&.aln-right {
justify-content: flex-end;
}
&.aln-top {
align-items: flex-start;
}
&.aln-middle {
align-items: center;
}
&.aln-bottom {
align-items: flex-end;
}
// Step through suffixes.
@each $suffix in $suffixes {
// Suffix.
@if ($suffix != '') {
$suffix: '-' + $suffix;
}
@else {
$suffix: '';
}
// Row.
// Important.
> .imp#{$suffix} {
order: -1;
}
// Columns, offsets.
@for $i from 1 through $cols {
> .col-#{$i}#{$suffix} {
width: $unit * $i;
}
> .off-#{$i}#{$suffix} {
margin-left: $unit * $i;
}
}
// Step through multipliers.
@each $multiplier in $multipliers {
// Gutters.
$class: null;
@if ($multiplier != 1) {
$class: '.gtr-' + ($multiplier * 100);
}
&#{$class} {
margin-top: ($guttersRows * $multiplier * -1);
margin-left: ($guttersCols * $multiplier * -1);
> * {
padding: ($guttersRows * $multiplier) 0 0 ($guttersCols * $multiplier);
}
// Uniform.
&.gtr-uniform {
margin-top: $guttersCols * $multiplier * -1;
> * {
padding-top: $guttersCols * $multiplier;
}
}
}
}
}
}

View File

@ -1,78 +0,0 @@
/// Makes an element's :before pseudoelement a FontAwesome icon.
/// @param {string} $content Optional content value to use.
/// @param {string} $category Optional category to use.
/// @param {string} $where Optional pseudoelement to target (before or after).
@mixin icon($content: false, $category: regular, $where: before) {
text-decoration: none;
&:#{$where} {
@if $content {
content: $content;
}
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
@if ($category == brands) {
font-family: 'Font Awesome 5 Brands';
}
@elseif ($category == solid) {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
@else {
font-family: 'Font Awesome 5 Free';
font-weight: 400;
}
}
}
/// Applies padding to an element, taking the current element-margin value into account.
/// @param {mixed} $tb Top/bottom padding.
/// @param {mixed} $lr Left/right padding.
/// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left)
/// @param {bool} $important If true, adds !important.
@mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) {
@if $important {
$important: '!important';
}
$x: 0.1em;
@if unit(_size(element-margin)) == 'rem' {
$x: 0.1rem;
}
padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important};
}
/// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp).
/// @param {string} $svg SVG data URL.
/// @return {string} Encoded SVG data URL.
@function svg-url($svg) {
$svg: str-replace($svg, '"', '\'');
$svg: str-replace($svg, '%', '%25');
$svg: str-replace($svg, '<', '%3C');
$svg: str-replace($svg, '>', '%3E');
$svg: str-replace($svg, '&', '%26');
$svg: str-replace($svg, '#', '%23');
$svg: str-replace($svg, '{', '%7B');
$svg: str-replace($svg, '}', '%7D');
$svg: str-replace($svg, ';', '%3B');
@return url("data:image/svg+xml;charset=utf8,#{$svg}");
}

View File

@ -1,33 +0,0 @@
// Misc.
$misc: (
z-index-base: 10000
);
// Duration.
$duration: (
navPanel: 0.5s
);
// Size.
$size: (
navPanel: 275px,
radius: 5px
);
// Font.
$font: (
);
// Palette.
$palette: (
bg: #f7f7f7,
fg: #474747,
fg-bold: #4c4c4c,
fg-light: #999,
border: #e0e0e0,
accent: (
bg: #37c0fb,
fg: #fff
)
);

View File

@ -1,376 +0,0 @@
// vendor.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Vendor prefixes.
/// @var {list}
$vendor-prefixes: (
'-moz-',
'-webkit-',
'-ms-',
''
);
/// Properties that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-properties: (
// Animation.
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-fill-mode',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
// Appearance.
'appearance',
// Backdrop filter.
'backdrop-filter',
// Background image options.
'background-clip',
'background-origin',
'background-size',
// Box sizing.
'box-sizing',
// Clip path.
'clip-path',
// Filter effects.
'filter',
// Flexbox.
'align-content',
'align-items',
'align-self',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'justify-content',
'order',
// Font feature.
'font-feature-settings',
'font-language-override',
'font-variant-ligatures',
// Font kerning.
'font-kerning',
// Fragmented borders and backgrounds.
'box-decoration-break',
// Grid layout.
'grid-column',
'grid-column-align',
'grid-column-end',
'grid-column-start',
'grid-row',
'grid-row-align',
'grid-row-end',
'grid-row-start',
'grid-template-columns',
'grid-template-rows',
// Hyphens.
'hyphens',
'word-break',
// Masks.
'mask',
'mask-border',
'mask-border-outset',
'mask-border-repeat',
'mask-border-slice',
'mask-border-source',
'mask-border-width',
'mask-clip',
'mask-composite',
'mask-image',
'mask-origin',
'mask-position',
'mask-repeat',
'mask-size',
// Multicolumn.
'break-after',
'break-before',
'break-inside',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
// Object fit.
'object-fit',
'object-position',
// Regions.
'flow-from',
'flow-into',
'region-fragment',
// Scroll snap points.
'scroll-snap-coordinate',
'scroll-snap-destination',
'scroll-snap-points-x',
'scroll-snap-points-y',
'scroll-snap-type',
// Shapes.
'shape-image-threshold',
'shape-margin',
'shape-outside',
// Tab size.
'tab-size',
// Text align last.
'text-align-last',
// Text decoration.
'text-decoration-color',
'text-decoration-line',
'text-decoration-skip',
'text-decoration-style',
// Text emphasis.
'text-emphasis',
'text-emphasis-color',
'text-emphasis-position',
'text-emphasis-style',
// Text size adjust.
'text-size-adjust',
// Text spacing.
'text-spacing',
// Transform.
'transform',
'transform-origin',
// Transform 3D.
'backface-visibility',
'perspective',
'perspective-origin',
'transform-style',
// Transition.
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
// Unicode bidi.
'unicode-bidi',
// User select.
'user-select',
// Writing mode.
'writing-mode',
);
/// Values that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-values: (
// Cross fade.
'cross-fade',
// Element function.
'element',
// Filter function.
'filter',
// Flexbox.
'flex',
'inline-flex',
// Grab cursors.
'grab',
'grabbing',
// Gradients.
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient',
// Grid layout.
'grid',
'inline-grid',
// Image set.
'image-set',
// Intrinsic width.
'max-content',
'min-content',
'fit-content',
'fill',
'fill-available',
'stretch',
// Sticky position.
'sticky',
// Transform.
'transform',
// Zoom cursors.
'zoom-in',
'zoom-out',
);
// Functions.
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Replaces a substring within another string.
/// @author Hugo Giraudel
/// @param {string} $string String.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {string} Updated string.
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/// Replaces a substring within each string in a list.
/// @param {list} $strings List of strings.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {list} Updated list of strings.
@function str-replace-all($strings, $search, $replace: '') {
@each $string in $strings {
$strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
}
@return $strings;
}
// Mixins.
/// Wraps @content in vendorized keyframe blocks.
/// @param {string} $name Name.
@mixin keyframes($name) {
@-moz-keyframes #{$name} { @content; }
@-webkit-keyframes #{$name} { @content; }
@-ms-keyframes #{$name} { @content; }
@keyframes #{$name} { @content; }
}
/// Vendorizes a declaration's property and/or value(s).
/// @param {string} $property Property.
/// @param {mixed} $value String/list of value(s).
@mixin vendor($property, $value) {
// Determine if property should expand.
$expandProperty: index($vendor-properties, $property);
// Determine if value should expand (and if so, add '-prefix-' placeholder).
$expandValue: false;
@each $x in $value {
@each $y in $vendor-values {
@if $y == str-slice($x, 1, str-length($y)) {
$value: set-nth($value, index($value, $x), '-prefix-' + $x);
$expandValue: true;
}
}
}
// Expand property?
@if $expandProperty {
@each $vendor in $vendor-prefixes {
#{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Expand just the value?
@elseif $expandValue {
@each $vendor in $vendor-prefixes {
#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Neither? Treat them as a normal declaration.
@else {
#{$property}: #{$value};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,233 +0,0 @@
:root{
--ButtonWidth:45%;
--ButtonHeight:45px;
}
#save {
right: 0;
position: absolute;
width: var(--ButtonWidth);
height: var(--ButtonHeight);
}
.layer > * {
/*height: fit-content;*/
}
/*input {*/
/* width: 100%;*/
/*}*/
.write_controllbox {
margin: 0;
padding: 0;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -moz-flex;
display: -webkit-flex;
display: flex;
justify-content: space-between;
list-style: none;
}
.write_option {
display: inline-block;
text-align: center;
line-height: 30px;
width: 100%;
padding: 0;
/*margin: 2px;*/
margin-top: 5px;
margin-bottom: 5px;
align-content: center;
top: 0;
bottom: 0;
color: black;
/*background: #40404564;*/
position: relative;
border: 1px solid #ccc;
height: var(--ButtonHeight);
border-radius: 10px;
background: #fafbfc;
}
#location_field {
line-height: initial;
height: initial;
}
/*#title_field {*/
/*font-size: 20px;*/
/*}*/
.pop_layer .pop_container {
padding: 20px 25px;
}
.pop_layer p.ctxt {
color: #666;
line-height: 25px;
}
.pop_layer .btn_r {
width: 100%;
margin: 10px 0 20px;
padding-top: 10px;
border-top: 1px solid #DDD;
text-align: right;
}
.pop_layer {
display: none;
position: absolute;
top: 50%;
left: 50%;
width: 410px;
height: auto;
background-color: #fff;
border: 5px solid #3571B5;
z-index: 10;
}
.dim_layer {
display: none;
position: fixed;
_position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
}
.dim_layer .dimBg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
opacity: .5;
filter: alpha(opacity=50);
}
.dim_layer .pop_layer {
display: block;
}
a.btn_layerClose {
display: inline-block;
height: 25px;
padding: 0 14px 0;
border: 1px solid #304a8a;
background-color: #3f5a9d;
font-size: 13px;
color: #fff;
line-height: 25px;
}
a.btn_layerClose:hover {
border: 1px solid #091940;
background-color: #1f326a;
color: #fff;
}
.post_layer {
height: 100%;
place-content: space-between;
place-items: stretch;
display: grid;
gap: 10px;
/*grid-auto-rows: minmax(200px, auto);*/
grid-template-columns: repeat(auto-fill, minmax(200px, auto));
width: 100%;
}
.post_item {
justify-content: space-between;
flex-wrap: wrap;
flex-direction: row;
width: 100%;
border-radius: 10px;
background: #F0F0F524;
}
#postId {
display: none;
}
#writeDate {
text-align: right;
}
.post_attr {
display: block;
padding: 5px;
}
#content{
overflow: hidden;
overflow-y: hidden;
overflow-x: hidden;
}
/*#editor {*/
/* border: 1px solid #393b42;*/
/* padding: 1px;*/
/* border-radius: 10px;*/
/* background: #00000044;*/
/*}*/
.ql-font-sans-serif { font-family: Arial, Helvetica, sans-serif; }
.ql-font-serif { font-family: Georgia, serif; }
.ql-font-monospace { font-family: "Courier New", monospace; }
.ql-font-arial { font-family: Arial, sans-serif; }
.ql-font-georgia { font-family: Georgia, serif; }
.ql-font-comic-sans-ms { font-family: "Comic Sans MS", cursive, sans-serif; }
.ql-font-courier-new { font-family: "Courier New", Courier, monospace; }
.ql-font-roboto {
font-family: 'Roboto', sans-serif;
}
.ql-font-playfair-display {
font-family: 'Playfair Display', serif;
}
/* Quill 툴바 기본 정렬 및 스타일 통합 */
.ql-toolbar.ql-snow {
display: flex;
flex-wrap: wrap; /* 툴바 요소가 넘칠 경우 줄바꿈 */
align-items: center;
gap: 8px; /* 아이콘과 버튼 간 간격 */
padding: 8px 12px;
border-radius: 10px 10px 0 0;
background: #fafbfc;
}
#editor.ql-container.ql-snow {
display: flex;
flex-wrap: wrap; /* 툴바 요소가 넘칠 경우 줄바꿈 */
align-items: center;
gap: 8px; /* 아이콘과 버튼 간 간격 */
padding: 8px 12px;
border-radius: 10px 10px 10px 10px;
background: #fafbfc;
}
/* 툴바 내 각 툴 그룹 사이 구분선 및 여백 */
.ql-toolbar.ql-snow > span.ql-formats:not(:last-child) {
border-right: 1px solid #ddd;
margin-right: 18px;
padding-right: 10px;
}
/* 폰트 선택기 및 드롭다운 스타일 */
.ql-toolbar .ql-picker-label,
.ql-toolbar .ql-picker {
min-width: 2em;
height: 2.2em;
display: flex;
align-items: center;
}
/* 툴바 버튼 스타일 */
.ql-toolbar button {
min-width: 32px;
height: 32px;
margin: 0 2px;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -1,198 +1,264 @@
:root {
--WindowFull : 99vw;
--TopHeight: 160px;
--FooterHeight: 160px;
--ContentVerticalMargin: 5px;
--DEFAULT_LAYER_BACK : #2e2e2eBB
/*background-image: url("data:image/svg+xml,<svg id='patternId' width='100%' height='100%' xmlns='http://www.w3.org/2000/svg'><defs><pattern id='a' patternUnits='userSpaceOnUse' width='45' height='51.96' patternTransform='scale(2) rotate(20)'><rect x='0' y='0' width='100%' height='100%' fill='%23202025ff'/><path d='M52.48 44.47a15 15 0 01-14.96 0 15 15 0 00-7.48 12.96M7.48 44.42a15 15 0 01-14.96 0M15 57.44c0-5.35-2.9-10.35-7.52-13.02a15 15 0 017.48-12.97M7.48 18.5a14.97 14.97 0 01-14.98-.03m15.02-.03A15 15 0 0115 5.47a15 15 0 00-4.4-10.62m23.8.05A15 15 0 0030 5.53a15 15 0 017.48 12.96 14.9 14.9 0 0015.02-.03m-22.5 13a15.13 15.13 0 017.52 13.01m-7.56-39a15 15 0 01-14.96 0M7.48 18.5a15 15 0 017.48 12.96 15 15 0 0015.04 0 15 15 0 017.48-12.96' stroke-width='3' stroke='%23ec914b8f' fill='none'/></pattern></defs><rect width='800%' height='800%' transform='translate(-38,-21.84)' fill='url(%23a)'/></svg>")*/
/*
* MODIFIED COMMON.CSS
* This file is refactored to inherit styles and variables from main.css.
* It styles custom components (popups, editor, etc.) to match the Arcana theme.
*/
/* * Removed conflicting global 'html, body' styles.
* The site will now correctly use the font and background from main.css.
*/
/*
* --- POPUP AND DIM LAYER STYLES ---
* Reworked to use variables and styles from main.css for a consistent look.
*/
.pop_layer {
display: none;
position: fixed; /* Use fixed for proper viewport centering */
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Modern centering method */
width: 450px;
max-width: 90%; /* Ensure it's responsive on small screens */
height: auto;
background-color: var(--pure-white, #fff); /* Use theme's white variable */
border: 1px solid #e0e0e0; /* Use theme's standard border color */
box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); /* Add a subtle shadow */
z-index: 1001; /* Ensure it's above the dim layer */
border-radius: 5px; /* Use theme's standard border-radius */
}
html {
/*background-image: url("data:image/svg+xml,<svg id='patternId' width='100%' height='100%' xmlns='http://www.w3.org/2000/svg'><defs><pattern id='a' patternUnits='userSpaceOnUse' width='46.5' height='46.5' patternTransform='scale(1) rotate(0)'><rect x='0' y='0' width='100%' height='100%' fill='%23000000ff'/><path d='M27.31-2.917a5 5 0 010 5.834m-8.12 0a5 5 0 010-5.834m-4.827 7.501a10 10 0 010-9.169m17.774.001a10 10 0 010 9.169M10.181 7.36a15 15 0 01-.001-14.722m26.14 0a15 15 0 010 14.724m-9.01 36.22a5 5 0 010 5.835m-8.12 0a5 5 0 010-5.834m-4.827 7.501a10 10 0 010-9.169m17.774.001a10 10 0 010 9.169M10.181 53.86a15 15 0 01-.001-14.723m26.14 0a15 15 0 010 14.724m6.12-27.693a5 5 0 010-5.834m-4.827 7.5a10 10 0 010-9.169M33.431 30.61a15 15 0 01-.001-14.722M4.06 20.332a5 5 0 010 5.835m4.827-7.501a10 10 0 010 9.169m4.183-11.947a15 15 0 010 14.724' stroke-linecap='square' stroke-width='1.5' stroke='%23c15d1ae3' fill='none'/><path d='M43.582 42.44a5 5 0 015.835 0m-7.501-4.827a10 10 0 019.169 0M39.138 33.43a15 15 0 0114.724 0m-56.781 9.01a5 5 0 015.836 0m-7.501-4.827a10 10 0 019.169 0M-7.362 33.43a15 15 0 0114.724 0M49.417 4.06a5 5 0 01-5.834 0m7.501 4.827a10 10 0 01-9.169 0m11.946 4.182a15 15 0 01-14.723.001M2.917 4.06a5 5 0 01-5.834 0m7.501 4.827a10 10 0 01-9.169 0M7.36 13.069a15 15 0 01-14.722.001m27.694 6.12a5 5 0 015.835 0m0 8.12a5 5 0 01-5.834 0m7.5 4.827a10 10 0 01-9.168 0m.001-17.774a10 10 0 019.169 0m2.776 21.956a15 15 0 01-14.723.001m0-26.14a15 15 0 0114.724 0' stroke-linecap='square' stroke-width='1.5' stroke='%23ffffff72' fill='none'/></pattern></defs><rect width='800%' height='800%' transform='translate(-93,-93)' fill='url(%23a)'/></svg>");*/
/*margin: 1vh 1vw;*/
background: black;
.pop_layer .pop_container {
padding: 2em;
}
/*#where{*/
/* table-layout: fixed;*/
/*}*/
/*.where_item {*/
/* display: table-cell;*/
/*}*/
body {
user-select: none;
-webkit-user-select: none;
align-content: center;
/*background: var(--DEFAULT_LAYER_BACK);*/
/*padding: 1vh 1vw;*/
/*border-radius: 10px;*/
.pop_layer .pop_conts h2 {
font-size: 1.75em; /* Match theme's h2 style */
margin-bottom: 1em;
text-align: center;
}
body > *{
align-content: center;
color: white;
padding: 1vh 1vw;
.pop_layer p.ctxt {
color: var(--font-color_default, #474747); /* Use theme's text color */
line-height: 1.65em;
}
/*#main_layer {*/
/* width: 100%;*/
/* margin: 0 auto;*/
/* position: relative;*/
/* background: #F0F0F524;*/
/* border-radius: 10px;*/
/*}*/
.center_menu {
display: inline-flex;
justify-content: space-evenly;
.pop_layer .btn_r {
width: 100%;
margin-top: 1.5em;
padding-top: 1em;
border-top: 1px solid #e0e0e0; /* Use theme's divider color */
text-align: right;
}
header {
/* Style the close button to match the theme's alternate button style */
a.btn_layerClose {
display: inline-block;
padding: 0 1.5em;
line-height: 2.75em;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
cursor: pointer;
background-color: var(--button-alt-default, #555);
color: var(--pure-white, #fff);
transition: background-color 0.2s ease-in-out;
}
a.btn_layerClose:hover {
background-color: var(--button-alt-hover, #626262);
color: var(--pure-white, #fff) !important; /* Ensure hover color override */
}
.dim_layer {
display: none;
position: fixed;
top: 0;
/*background: var(--DEFAULT_LAYER_BACK);*/
background: var(--DEFAULT_LAYER_BACK);
border-top: #ec914b8f;
border-radius: 5px 30px;
border-width: 1px;
height: 5vh;
min-height: 5vh;
grid-auto-flow: column;
display: flex;;
position: relative;
justify-content: space-around;
}
.user_info {
padding: 2px;
position: absolute;
display: inline-flex;
flex-direction: column;
right: 0;
}
.login_input {
border-radius: 10px;
border-width: 2px;
border: #F0F0F514;
padding: 5px;
background: #F0F0F524;
color: white;
text-align: center;
margin: 2px;
}
.login_input::placeholder {
color: #ec914b;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.4); /* Slightly softer dim */
}
.login_input::-webkit-input-placeholder{
color: #ec914b;
/* * --- LOGIN FORM STYLES ---
* Adapted to use the default form input styles from main.css.
*/
#loginFormElement input[type="text"],
#loginFormElement input[type="password"] {
margin-bottom: 1em; /* Add spacing between fields */
}
.login_input:-ms-input-placeholder{
color: #ec914b;
#loginFormElement button {
margin-top: 1em;
width: 100%;
}
.hello_to_user {
border-radius: 10px;
border-width: 2px;
border: #F0F0F514;
padding: 10px;
background: #F0F0F524;
color: #ec914b;
text-align: center;
margin: 2px;
}
.hello_to_user_txt {
color: #ec914b;
}
#bottom {
float: right;
display: inline-block;
justify-content: space-between;
margin-left: auto;
grid-auto-flow: column;
grid-template-columns: 3fr;
position: absolute;
right: 30px;
}
#top {
display: inline-block;
justify-content: space-between;
margin-left: auto;
grid-auto-flow: column;
grid-template-columns: 3fr;
position: absolute;
left: 30px;
}
h2 {
margin: 1vw;
}
#main_layer {
border-radius: 10px 10px 0px 10px;
padding: 10px;
margin: 1vw 1vh;
position: relative;
overflow-y: auto;
overflow-x: clip;
height: 78vh;
min-height: 8vh;
}
#main_layer > div {
position: relative;
}
#content > * {
}
footer {
display: flex;
bottom: 0;
border-top: #ec914b8f;
background: var(--DEFAULT_LAYER_BACK);
border-radius: 5px 30px ;
border-width: 1px;
height: 5vh;
min-height: 5vh;
position: relative;
/* Custom Checkbox Styling */
#loginFormElement span {
vertical-align: middle;
margin-left: 0.5em;
}
#rememberMe {
width: 20px;
height: 20px;
background-color: lightblue; /* 체크박스 배경색 */
border: 2px solid blue; /* 테두리 색 */
appearance: none; /* 기본 OS 스타일 제거 */
vertical-align: middle;
width: 22px;
height: 22px;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
border: 1px solid #e0e0e0;
border-radius: 5px;
background-color: var(--pure-white, #fff);
cursor: pointer;
position: relative;
top: -2px;
}
#rememberMe:checked {
background-color: blue;
background-color: var(--point-color, #FFA500);
border-color: var(--point-color, #FFA500);
}
#rememberMe:checked::after {
content: "";
content: '';
position: absolute;
top: 3px;
top: 2px;
left: 7px;
width: 5px;
height: 10px;
border: solid white;
width: 6px;
height: 12px;
border: solid var(--pure-white, #fff);
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* * --- BLOG POST & EDITOR STYLES ---
*/
/* Control box below the editor in viewer.html */
.write_controllbox {
margin-top: 2em;
padding: 0;
display: flex;
justify-content: space-between;
list-style: none;
gap: 15px; /* Use gap for spacing */
}
.write_option {
display: flex; /* Use flexbox for centering */
justify-content: center;
align-items: center;
text-align: center;
width: 100%;
padding: 0.75em;
margin: 0;
background: var(--pure-white, #fff);
border: solid 1px #e0e0e0;
border-radius: 5px;
color: inherit;
min-height: 48px; /* Set a minimum height */
}
/* * --- QUILL EDITOR THEME OVERRIDE ---
* Modified to blend with the Arcana theme's light background.
*/
/* Define custom fonts for Quill to match the site */
.ql-font-source-sans-pro { font-family: 'Source Sans Pro', sans-serif; }
/* Add any other fonts you've whitelisted in common.js */
.ql-toolbar.ql-snow {
background: var(--almost-white, #f7f7f7); /* Use theme's light gray */
border: 1px solid #e0e0e0;
border-bottom: none; /* Connect toolbar to editor visually */
border-radius: 5px 5px 0 0;
padding: 12px 8px;
}
.ql-container.ql-snow {
background: var(--pure-white, #fff);
border: 1px solid #e0e0e0;
border-radius: 0 0 5px 5px;
color: var(--font-color_default, #474747);
}
/* Ensure editor content uses the theme's default font and size */
.ql-editor {
font-family: 'Source Sans Pro', sans-serif;
font-size: 1rem; /* Base size */
line-height: 1.65em;
}
/*
* --- COMMENT SECTION ---
* Styled to fit the main theme.
*/
.comment-section {
margin-top: 3em;
padding-top: 2em;
border-top: 1px solid #e0e0e0;
}
#comment-form-container {
display: flex;
flex-direction: column;
margin-bottom: 2em;
}
/* Inherits styles from main.css 'textarea' selector */
#comment-input {
min-height: 100px;
margin-bottom: 1em;
}
#comments-list .comment {
background: var(--almost-white, #f7f7f7);
border-radius: 5px;
padding: 1em 1.5em;
margin-bottom: 1em;
border: 1px solid #e0e0e0;
}
.comment-header {
font-weight: 600;
color: var(--font-color_default, #474747);
margin-bottom: 0.5em;
}
.comment-time {
font-size: 0.9em;
color: #999;
margin-left: 0.5em;
}
.comment-content {
line-height: 1.65em;
white-space: pre-wrap;
word-wrap: break-word;
}
/* 읽기 모드 컨트롤 박스 내부의 태그 스타일 */
.write_option .tag-title {
font-weight: 600;
margin-right: 0.5em;
color: #555;
}
.write_option .tag-item {
display: inline-block;
background-color: var(--almost-white, #f7f7f7);
border: 1px solid #e0e0e0;
border-radius: 15px; /* 둥근 태그 모양 */
padding: 0.2em 0.8em;
margin-right: 0.5em;
font-size: 0.9em;
color: var(--font-color_default, #474747);
}
/* 읽기 모드에서는 커서 모양을 기본으로 변경 */
.write_option.controlbox-category:not(.btn-example),
.write_option.controlbox-hashtag:not(.btn-example) {
cursor: default;
}

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 470 KiB

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -2693,7 +2693,7 @@ button.small,
/* Banner */
#banner {
background-image: url("../../images/banner.jpg");
background-image: url("../images/banner.jpg");
background-position: center center;
background-size: cover;
height: 28em;

View File

@ -1,40 +1,17 @@
/* src/main/resources/static/css/spider.css */
/*
* 전체 게임 컨테이너 (Canvas를 담는 역할)
* - 기존 블로그 레이아웃을 해치지 않도록 최소한의 스타일만 적용
*/
#game-container {
display: flex;
justify-content: center;
align-items: center;
justify-content: center; /* 가로 중앙 정렬 */
align-items: flex-start; /* 세로 상단 정렬 */
background-color: #008000;
width: 100vw;
height: 100vh;
box-sizing: border-box;
}
/*
* 캔버스 스타일
* - 배경을 초록색으로 설정하고, 테두리를 추가
* - 화면 크기에 맞춰 비율을 유지하며 최대 크기 제한
*/
#gameCanvas {
background-color: #008000;
border: 2px solid #fff;
max-width: 90vw;
max-height: 90vh;
width: 95%;
max-height: min(95vw, 95vh);
box-sizing: border-box;
}
/*
* 카드 이동 애니메이션 (Canvas 로직에서는 사용하지 않지만, 향후 재사용을 위해 남겨둠)
*/
@keyframes moveCard {
from {
transform: translate(var(--fromX), var(--fromY));
}
to {
transform: translate(var(--toX), var(--toY));
}
}

View File

@ -9,7 +9,7 @@
<title>Arcana by HTML5 UP</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="css/main.css" />
</head>
<body class="is-preload">
<div id="page-wrapper">
@ -228,12 +228,12 @@
</div>
<!-- Scripts -->
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/jquery.dropotron.min.js"></script>
<script src="assets/js/browser.min.js"></script>
<script src="assets/js/breakpoints.min.js"></script>
<script src="assets/js/util.js"></script>
<script src="assets/js/main.js"></script>
<script src="js/jquery.min.js"></script>
<script src="js/jquery.dropotron.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/breakpoints.min.js"></script>
<script src="js/util.js"></script>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,335 +0,0 @@
var quill = null
function initEditor(useEditor) {
console.log("DOMContentLoaded");
const editorContainer = document.querySelector('#editor');
function setEditorHeight() {
const height = Math.max(window.innerHeight * 0.5, 300);
editorContainer.style.height = height + 'px';
}
baseData.id = serverData.id;
baseData.title = decodeURIComponent(serverData.title || '');
baseData.content = decodeURIComponent(serverData.content || '');
baseData.firstPostLat = serverData.firstPostLat;
baseData.firstPostLon = serverData.firstPostLon;
baseData.writeTime = serverData.writeTime;
baseData.originId = serverData.originId;
getLocation();
setEditorHeight();
window.addEventListener('resize', setEditorHeight);
try {
var Font = Quill.import('formats/font');
Font.whitelist = ['sans-serif', 'serif', 'monospace', 'arial', 'georgia', 'comic-sans-ms', 'courier-new', 'roboto', 'playfair-display'];
Quill.register(Font, true);
Quill.register({ 'modules/table-better': QuillTableBetter }, true);
quill = new Quill(editorContainer, {
theme: 'snow',
modules: useEditor ? {
toolbar: {
container: [
[{ font: Font.whitelist }],
[{ 'size': ['small', false, 'large', 'huge'] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'header': 1 }, { 'header': 2 }, 'blockquote', 'code-block'],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
['link', 'image', 'video'],
['table-better'],
[{ 'direction': 'rtl' }],
[{ 'align': [] }],
['clean']
],
handlers: {
image: function () {
selectLocalImage();
},
video: function () {
selectLocalVideo()
}
}
},
'table-better': {
language: 'en_US',
toolbarTable: true
},
keyboard: {
bindings: QuillTableBetter.keyboardBindings
}
} : {toolbar : false, toolbarTable: false},
readOnly: !useEditor
});
loadContent(serverData.content);
if (!useEditor) {
editorContainer.classList.add('readonly-mode');
} else {
editorContainer.classList.remove('readonly-mode');
}
console.log("quill", quill);
}catch (e) { }
try {
document.querySelector("#title_field").textContent = baseData.title
}catch (e) { }
try {
document.getElementById('location_field').textContent = "Lat: " + baseData.firstPostLat + ", Lon: " + baseData.firstPostLon;
var requestOptions = {
method: 'GET',
};
fetch("https://api.geoapify.com/v1/geocode/reverse?lat="+baseData.firstPostLat+"&lon="+baseData.firstPostLon+"&apiKey=2b37a75bb0754086b5a1c4a7c3173ee8", requestOptions)
.then(response => response.json())
.then(function(result) {
try {
document.getElementById('location_field').textContent = result.features[0].properties.formatted
} catch (e) {
document.getElementById('location_field').innerHTML = "Lat: " + baseData.firstPostLat + "<br> Lon: " + baseData.firstPostLon;
}
})
.catch(error => console.log('error', error));
}catch (e) { }
}
function parseDelta(content) {
try {
if (typeof content === "string") {
const obj = JSON.parse(content);
if (obj && typeof obj === "object" && Array.isArray(obj.ops)) {
return obj; // 유효한 Delta 객체
}
} else if (content && typeof content === "object" && Array.isArray(content.ops)) {
return content; // 이미 Delta 객체 형태
}
} catch (e) {
// JSON 파싱 실패 → HTML로 간주
}
return null; // Delta 아님
}
function loadEditor() {
location.href = getMainPath() + '/blog/editor/' + serverData.id;
}
function loadContent(content) {
console.log("content >>> ", content);
const delta = parseDelta(content);
if (delta) {
quill.setContents(delta);
} else {
quill.clipboard.dangerouslyPasteHTML(content);
}
}
function save() {
onclickWrite(serverData.enc ,serverData.keyword,JSON.stringify(quill.getContents()))
}
function parseDeltaContent(deltaString) {
let textContent = "";
let mediaLinks = [];
try {
const delta = JSON.parse(deltaString); // Delta 문자열 → 객체
if (delta && Array.isArray(delta.ops)) {
delta.ops.forEach(op => {
if (op.insert) {
if (typeof op.insert === "string") {
textContent += op.insert;
} else if (typeof op.insert === "object") {
if (op.insert.image) {
mediaLinks.push(op.insert.image);
}
if (op.insert.video) {
mediaLinks.push(op.insert.video);
}
}
}
});
}
} catch (e) {
console.error("Delta JSON parse error:", e);
}
return { text: textContent.trim(), media: mediaLinks };
}
function selectLocalImage() {
// 이미지 URL 입력 받기
const url = prompt("이미지 URL을 입력하거나 빈칸으로 두시면 파일 업로드를 합니다.");
if (url) {
// URL이 입력된 경우 이미지 삽입
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', url);
quill.setSelection(range.index + 1);
} else {
// URL이 없거나 취소한 경우 파일 업로드 처리
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files[0];
if (file) {
const file = input.files[0];
console.log("on selectLocalImage File", file);
if (!file || !file.type.startsWith('image/')) {
console.warn('이미지 파일만 업로드 가능합니다.');
return;
}
uploadImage(file);
}
};
}
}
function uploadImage(blob) {
const formData = new FormData();
formData.append('file', blob);
let uploadUrl = getMainPath() + "/blog/post/imageUpload.bjx";
let imageUrl = getMainPath() + '/blog/post/images/';
$.ajax({
type: 'POST',
enctype: 'multipart/form-data',
url: uploadUrl,
data: formData,
dataType: 'json',
processData: false,
contentType: false,
cache: false,
timeout: 600000,
success: function (data) {
console.log(data);
imageUrl += data.fileName;
insertToEditor(imageUrl);
},
error: function (e) {
console.error(e);
// callback('image_load_fail');
}
});
}
function selectLocalVideo() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'video/*');
input.click();
input.onchange = () => {
const file = input.files[0];
if (!file || !file.type.startsWith('video/')) {
alert('동영상 파일만 업로드할 수 있습니다.');
return;
}
uploadVideo(file);
};
}
function uploadVideo(file) {
const formData = new FormData();
formData.append('video', file);
fetch('/api/upload/video', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
if (result.url) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'video', result.url);
quill.setSelection(range.index + 1);
} else {
console.error('동영상 업로드 실패', result);
}
})
.catch(err => {
console.error('업로드 중 오류', err);
});
}
function insertToEditor(url) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', url);
quill.setSelection(range.index + 1);
}
var currentLat = 0.0
var currentLon = 0.0
let baseData = {
'id' : "",
'title': "",
'content': "",
'firstPostLat': 0.0,
'firstPostLon': 0.0,
'category' : "none",
'hashTags' : "#none",
'modifyLat' : 0.0,
'modifyLon' : 0.0,
'originId' : "",
'writeTime' : 0,
}
function goToEditor(element) {
const postId = element.getAttribute('data-post-id');
if (postId) {
// postId를 이용해 원하는 처리 수행
console.log("편집할 postId:", postId);
// 예: 페이지 이동, 모달 오픈 등
location.href = getMainPath() + '/blog/editor/' + postId;
} else {
console.warn("postId가 없네요.");
}
}
function onclickWrite(type, keyword, html) {
let title_field = document.getElementById('title_field')
var hasValues = true
if (hasValues) {
baseData.title = encodeURIComponent(title_field.value)
baseData.content = encodeURIComponent(html)
baseData.modifyLat = encodeURIComponent(currentLat)
baseData.modifyLon = encodeURIComponent(currentLon)
}
let uploadUrl = getMainPath() + "/blog/post.bjx";
if(confirm(JSON.stringify(baseData) + "\n해당 내용으로\n유저 등록 하실??")) {
post(uploadUrl,type,JSON.stringify(baseData),keyword, function (resultData) {
alert(resultData)
})
} else {
}
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
x.innerHTML = "Geolocation is not supported by this browser.";
}
}
function showPosition(position) {
currentLat = position.coords.latitude
currentLon = position.coords.longitude
if(baseData.firstPostLat !== 0.0) {
baseData.modifyLat = encodeURIComponent(currentLat)
} else {
baseData.firstPostLat = encodeURIComponent(currentLat)
}
if(baseData.firstPostLon !== 0.0 ) {
baseData.modifyLon = encodeURIComponent(currentLon)
} else {
baseData.firstPostLon = encodeURIComponent(currentLon)
}
document.getElementById('location_field').textContent = "Lat: " + position.coords.latitude + ", Lon: " + position.coords.longitude;
}

View File

@ -1,464 +1,546 @@
var enc = null
var keyword = null
document.addEventListener('DOMContentLoaded', function() {
const loginForm = document.getElementById('loginFormElement');
loginForm.addEventListener('submit', function(e) {
e.preventDefault(); // 기본 폼 제출 동작 방지
submitLoginForm();
});
/**
* =================================================================================
* common.js - 블로그 공통 스크립트 (최종 수정본)
* - Quill 에디터 초기화 제어 (편집/읽기 모드)
* - 게시물 데이터 관리 (baseData) 서버 통신 (save, post)
* - UI 제어 (팝업, 컨트롤 박스 동적 설정)
* - 페이지 이동 로그인/로그아웃, 유틸리티 함수
* =================================================================================
*/
// 전역 변수: Quill 에디터 인스턴스와 게시물 기본 데이터를 저장합니다.
var quill = null;
var currentLat = 0.0;
var currentLon = 0.0;
var baseData = {
'id': "",
'title': "",
'content': "",
'category': "none",
'tags': "",
'firstPostLat': 0.0,
'firstPostLon': 0.0,
'modifyLat': 0.0,
'modifyLon': 0.0,
'originId': "",
'writeTime': 0,
};
// jQuery를 사용하여 문서가 완전히 로드된 후에 함수를 실행합니다.
$(document).ready(function() {
// 뷰어/에디터 페이지가 아닐 수 있으므로, #editor 요소가 있을 때만 initEditor를 호출하도록 방어 코드를 추가하는 것이 좋습니다.
// 현재는 각 페이지에서 직접 호출하므로 이 코드는 참고용입니다.
// 사이드바의 인기글/최신글 목록을 가져옵니다.
if (document.querySelector(".rank_of_view")) {
fetch('blog/rankOfViews.bjx')
.then(response => response.json())
.then(data => {
const ul = document.querySelector('.rank_of_view');
ul.innerHTML = ''; // 기존 리스트 지움
ul.style.listStyle = 'none'; // 불릿 제거
ul.style.paddingLeft = '0'; // 들여쓰기 제거 (선택사항)
// data가 ['제목1', '제목2', ...] 형식이라고 가정
data.posts.forEach(item => {
const date = new Date(item.writeTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}/${month}/${day}`;
const li = document.createElement('li');
const a = document.createElement('a');
a.id = item.id;
a.href = `${getMainPath()}/blog/viewer/${item.id}`;
a.innerHTML = `${item.title}<br>[${year}/${month}/${day}]`
li.appendChild(a);
ul.appendChild(li);
});
})
.catch(error => {
console.error('받아오기 실패:', error);
}
);
fetchRankOfViews();
}
if (document.querySelector(".recent_posts")) {
fetch('blog/recentOfPost.bjx')
.then(response => response.json())
.then(data => {
const ul = document.querySelector('.recent_posts');
ul.innerHTML = ''; // 기존 리스트 지움
ul.style.listStyle = 'none'; // 불릿 제거
ul.style.paddingLeft = '0'; // 들여쓰기 제거 (선택사항)
// data가 ['제목1', '제목2', ...] 형식이라고 가정
data.posts.forEach(item => {
const date = new Date(item.writeTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}/${month}/${day}`;
const li = document.createElement('li');
const a = document.createElement('a');
a.id = item.id;
a.href = `${getMainPath()}/blog/viewer/${item.id}`;
a.innerHTML = `${item.title}<br>[${year}/${month}/${day}]`
li.appendChild(a);
ul.appendChild(li);
});
})
.catch(error => {
console.error('받아오기 실패:', error);
}
);
fetchRecentPosts();
}
// 팝업 닫기 버튼 이벤트
$('.btn_layerClose').on('click', function(e) {
e.preventDefault();
closePopup();
});
// 로그인 폼 제출 이벤트
$('#loginFormElement').on('submit', function(e) {
e.preventDefault();
submitLoginForm();
});
// 로그인 팝업 열기 버튼 이벤트
$('.open-login-popup').on('click', function() {
openPopup(this);
});
});
onload = function() {
history.replaceState({}, null, location.pathname);
// var accToken = get_cookie("access")
// var refreshToken = get_cookie("refresh")
// console.log("access === " + accToken + " || " + accToken.length);
// console.log("refresh === " + refreshToken + " || " + refreshToken.length);
document.cookie = "access=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
document.cookie = "CLEAR=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
var currentList = [{"page":["posts"],"id":"menu_posts"},{"page":["licenses"],"id":"menu_drop"},{"page":["licenses"],"id":"menu_drop"}]
if(location.pathname.length > 1) {
// 1. 모든 'current' 클래스를 가진 요소를 선택하고 제거
// const currentElements = document.querySelectorAll('.current');
// currentElements.forEach(element => {
// element.classList.remove('current');
// });
currentList.forEach(element => {
element.page.forEach((page, index) => {
console.log(location.pathname);
if (location.pathname.includes(page)) {
const targetElement = document.getElementById(element.id);
if (targetElement) {
targetElement.classList.add('current');
}
}
})
})
} else {
const targetElement = document.getElementById('menu_home');
if (targetElement) {
targetElement.classList.add('current');
/**
* [핵심] Quill 에디터를 초기화하는 메인 함수입니다.
* useEditor 파라미터 값에 따라 '편집 모드' '읽기 모드' 동적으로 전환합니다.
* @param {boolean} useEditor - true: 편집기 활성화, false: 읽기 전용 뷰어 활성화
*/
function initEditor(useEditor = false) {
console.log("### initEditor 함수 실행됨! 편집 모드:", useEditor, "###"); // 이 줄을 추가!
const editorContainer = document.querySelector('#editor');
if (!editorContainer) return;
if (typeof serverData !== 'undefined') {
baseData.id = serverData.id;
baseData.title = decodeURIComponent(serverData.title || '');
baseData.content = decodeURIComponent(serverData.content || '');
baseData.category = serverData.category;
baseData.tags = serverData.tags;
baseData.firstPostLat = serverData.firstPostLat;
baseData.firstPostLon = serverData.firstPostLon;
baseData.writeTime = serverData.writeTime;
baseData.originId = serverData.originId;
}
getLocation();
try {
var Font = Quill.import('formats/font');
Font.whitelist = ['sans-serif', 'serif', 'monospace', 'arial', 'georgia', 'comic-sans-ms', 'courier-new', 'roboto', 'playfair-display'];
Quill.register(Font, true);
Quill.register({ 'modules/table-better': QuillTableBetter }, true);
const quillOptions = {
theme: 'snow',
modules: useEditor ? {
toolbar: {
container: [
[{ font: Font.whitelist }], [{ 'size': ['small', false, 'large', 'huge'] }],
['bold', 'italic', 'underline', 'strike'], [{ 'color': [] }, { 'background': [] }],
[{ 'header': 1 }, { 'header': 2 }, 'blockquote', 'code-block'],
[{ 'script': 'sub'}, { 'script': 'super' }], [{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'indent': '-1'}, { 'indent': '+1' }], ['link', 'image', 'video'],
['table-better'], [{ 'direction': 'rtl' }], [{ 'align': [] }], ['clean']
],
handlers: { image: function() { selectLocalImage(); }, video: function() { selectLocalVideo(); } }
},
'table-better': { language: 'en_US', toolbarTable: true },
keyboard: { bindings: QuillTableBetter.keyboardBindings }
} : {
toolbar: false
},
readOnly: !useEditor
};
quill = new Quill(editorContainer, quillOptions);
if (baseData.content) {
loadContent(baseData.content);
}
}
}
// onbeforeunload = function () {
// var accToken = get_cookie("access")
// var refreshToken = get_cookie("refresh")
// console.log("access === " + accToken + " || " + accToken.length);
// console.log("refresh === " + refreshToken + " || " + refreshToken.length);
//
// if (accToken.length < 1) {
// document.cookie = "refresh="+ window.sessionStorage.getItem("REFRESH") + ";";
// }
// if (refreshToken.length < 1) {
// window.sessionStorage.setItem("REFRESH",get_cookie("refresh"))
// }
// }
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) {
alert("서버에 전달됨.")
})
if (!useEditor) {
editorContainer.classList.add('readonly-mode');
} else {
}
}
return false
}
function get_cookie(name) {
var value = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
return value? value[2] : null;
}
function divider(key) {
return merge(padding(),key,padding())
}
function padding() {
return "%7C%2A-%2A%7C";
}
function merge(...args) {
return args.join("");
}
function unformat(type , data, key) {
var even = [];
var odd = [];
data.split("").forEach(function (v,idx,full) {if(idx % 2 === 0) {even.push(v)} else {odd.push(v)}});
switch (type) {
case "T0": return merge(odd.join(""),divider(key),even.join(""));
case "T1": return merge(odd.reverse().join(""),divider(key),even.join(""));
case "T2": return merge(odd.join(""),divider(key),even.reverse().join(""));
default: return merge(odd.reverse().join(""),divider(key),even.reverse().join(""));
}
}
function checkDebug(){
var debug = false
try {
debug = typeof v8debug === 'object'
|| /--debug|--inspect/.test(process.execArgv.join(' '));
alert(debug)
} catch (e) {
}
try {
const inspector = require('inspector');
debug = inspector.url() !== undefined;
alert(debug)
} catch (e) {
}
}
var acpt_key = ""
function post(target,type, data, key,callBackResult) {
var httpRequest;
/* 통신에 사용 될 XMLHttpRequest 객체 정의 */
httpRequest = new XMLHttpRequest();
/* httpRequest의 readyState가 변화했을때 함수 실행 */
httpRequest.onreadystatechange = () => {
/* readyState가 Done이고 응답 값이 200일 때, 받아온 response로 name과 age를 그려줌 */
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
callBackResult(httpRequest.response)
} else {
alert('Request Error!');
editorContainer.classList.remove('readonly-mode');
const titleField = document.querySelector("#title_field");
if (titleField) {
titleField.value = baseData.title;
}
}
} catch (e) {
console.error("Quill initialization failed:", e);
}
httpRequest.open('POST', target, true);
httpRequest.setRequestHeader("Content-Type", "text/plain");
var odd = []
var even = []
var dataStr = JSON.stringify(data)
var src = dataStr.split("")
src.forEach(function (s,i,a) {if (i % 2 === 0) {even.push(s)} else {odd.push(s)}})
httpRequest.send(btoa(JSON.stringify({
'data': unformat(type,data,key),
'key':key,
'type':type,
})));
}
function postLogin(target,type, data, key,callBackResult) {
var httpRequest;
/* 통신에 사용 될 XMLHttpRequest 객체 정의 */
httpRequest = new XMLHttpRequest();
/* httpRequest의 readyState가 변화했을때 함수 실행 */
httpRequest.onreadystatechange = () => {
/* readyState가 Done이고 응답 값이 200일 때, 받아온 response로 name과 age를 그려줌 */
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
try {
var data = JSON.parse(httpRequest.response)
callBackResult(data)
} catch (e) {
}
} else {
alert('Request Error!');
}
}
}
httpRequest.withCredentials = true
httpRequest.open('POST', target, true);
httpRequest.setRequestHeader("Content-Type", "text/plain");
var odd = []
var even = []
var dataStr = JSON.stringify(data)
var src = dataStr.split("")
src.forEach(function (s,i,a) {if (i % 2 === 0) {even.push(s)} else {odd.push(s)}})
httpRequest.send(btoa(JSON.stringify({
'data': unformat(type,data,key),
'key':key,
'type':type,
})));
setupControlBox(useEditor ? 'edit' : 'view');
}
function mainPath() {
console.log(`location.port >> ${location.port}`)
if ('443' === location.port) {
document.location.replace(location.protocol + "//" + location.hostname + ":" + location.port)
function selectLocalImage() {
// 이미지 URL 입력 받기
const url = prompt("이미지 URL을 입력하거나 빈칸으로 두시면 파일 업로드를 합니다.");
if (url) {
// URL이 입력된 경우 이미지 삽입
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', url);
quill.setSelection(range.index + 1);
} else {
document.location.replace(location.protocol + "//" + location.hostname)
// URL이 없거나 취소한 경우 파일 업로드 처리
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files[0];
if (file) {
const file = input.files[0];
console.log("on selectLocalImage File", file);
if (!file || !file.type.startsWith('image/')) {
console.warn('이미지 파일만 업로드 가능합니다.');
return;
}
uploadImage(file);
}
};
}
}
function gotoWrite() {
document.location.replace(getMainPath()+"/blog/write.bs")
function uploadImage(blob) {
const formData = new FormData();
formData.append('file', blob);
let uploadUrl = getMainPath() + "/blog/post/imageUpload.bjx";
let imageUrl = getMainPath() + '/blog/post/images/';
$.ajax({
type: 'POST',
enctype: 'multipart/form-data',
url: uploadUrl,
data: formData,
dataType: 'json',
processData: false,
contentType: false,
cache: false,
timeout: 600000,
success: function (data) {
console.log(data);
imageUrl += data.fileName;
insertToEditor(imageUrl);
},
error: function (e) {
console.error(e);
// callback('image_load_fail');
}
});
}
function gotoModify() {
document.location.replace(getMainPath()+"/blog/modify.bs")
}
function selectLocalVideo() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'video/*');
input.click();
function gotoPuzzleUpload() {
document.location.replace(getMainPath()+"/puzzle/upload.bs")
input.onchange = () => {
const file = input.files[0];
if (!file || !file.type.startsWith('video/')) {
alert('동영상 파일만 업로드할 수 있습니다.');
return;
}
uploadVideo(file);
};
}
function uploadVideo(file) {
const formData = new FormData();
formData.append('video', file);
fetch('/api/upload/video', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(result => {
if (result.url) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'video', result.url);
quill.setSelection(range.index + 1);
} else {
console.error('동영상 업로드 실패', result);
}
})
.catch(err => {
console.error('업로드 중 오류', err);
});
}
function gotoSudoKuGen() {
document.location.replace(getMainPath()+"/puzzle/sudoku_gen.bs")
function insertToEditor(url) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', url);
quill.setSelection(range.index + 1);
}
function gotoWhere() {
document.location.replace(getMainPath()+"/bums/where.bs")
/**
* 에디터 모드('edit' 또는 'view') 따라 컨트롤 박스를 설정합니다.
*/
function setupControlBox(mode) {
const categoryBox = document.querySelector('.controlbox-category');
const hashtagBox = document.querySelector('.controlbox-hashtag');
if (!categoryBox || !hashtagBox) return;
if (mode === 'edit') {
categoryBox.setAttribute('onclick', 'openPopup(this)');
hashtagBox.setAttribute('onclick', 'openPopup(this)');
categoryBox.innerText = '카테고리 설정';
hashtagBox.innerText = '해시태그 편집';
fetchCategoriesAndHashtags();
} else {
categoryBox.removeAttribute('onclick');
hashtagBox.removeAttribute('onclick');
categoryBox.classList.remove('btn-example');
hashtagBox.classList.remove('btn-example');
categoryBox.innerHTML = `<span class="tag-title">카테고리: </span><span class="tag-item">${baseData.category || '지정되지 않음'}</span>`;
hashtagBox.innerHTML = '<span class="tag-title">태그: </span>';
if (baseData.tags && baseData.tags.length > 0) {
baseData.tags.split(',').forEach(tag => {
hashtagBox.innerHTML += `<span class="tag-item">#${tag.trim()}</span>`;
});
} else {
hashtagBox.innerHTML += '<span>없음</span>';
}
}
}
/**
* 백엔드 API를 호출하여 카테고리와 해시태그 목록을 가져와 팝업을 채웁니다.
*/
function fetchCategoriesAndHashtags() {
fetch(`${getMainPath()}/blog/categories.bjx`).then(res => res.json()).then(data => {
if (data.resultCode === 0 && data.tags) {
const list = document.querySelector('#category-list');
if(list) {
list.innerHTML = '';
data.tags.forEach(tag => {
const el = document.createElement('span');
el.className = 'tag-item';
el.innerText = tag;
list.appendChild(el);
});
}
}
}).catch(err => console.error('Error fetching categories:', err));
fetch(`${getMainPath()}/blog/hashtags.bjx`).then(res => res.json()).then(data => {
if (data.resultCode === 0 && data.tags) {
const list = document.querySelector('#hashtag-list');
if(list) {
list.innerHTML = '';
data.tags.forEach(tag => {
const el = document.createElement('span');
el.className = 'tag-item';
el.innerText = `#${tag}`;
list.appendChild(el);
});
}
}
}).catch(err => console.error('Error fetching hashtags:', err));
}
/**
* 컨텐츠를 Quill 에디터에 로드합니다.
*/
function loadContent(content) {
try {
const delta = JSON.parse(content);
if (delta && Array.isArray(delta.ops)) {
quill.setContents(delta);
return;
}
} catch (e) { /* HTML 문자열일 경우 아래에서 처리 */ }
quill.clipboard.dangerouslyPasteHTML(content);
}
/**
* 게시물 수정 페이지로 이동합니다.
*/
function loadEditor() {
if (baseData.id) {
location.href = `${getMainPath()}/blog/edit/${baseData.id}`;
}
}
/**
* 작성된 게시물을 서버에 저장합니다.
*/
function save() {
const titleField = document.getElementById('title_field');
if (titleField) {
baseData.title = encodeURIComponent(titleField.value);
}
baseData.content = encodeURIComponent(JSON.stringify(quill.getContents()));
baseData.modifyLat = currentLat;
baseData.modifyLon = currentLon;
const uploadUrl = `${getMainPath()}/blog/post.bjx`;
if (confirm("해당 내용으로 저장하시겠습니까?")) {
post(uploadUrl, serverData.enc, JSON.stringify(baseData), serverData.keyword, function(resultData) {
alert("저장되었습니다.");
});
}
}
/**
* 사용자의 현재 위치(위도, 경도) 가져옵니다.
*/
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => {
currentLat = pos.coords.latitude;
currentLon = pos.coords.longitude;
if (baseData.firstPostLat === 0.0) baseData.firstPostLat = currentLat;
if (baseData.firstPostLon === 0.0) baseData.firstPostLon = currentLon;
baseData.modifyLat = currentLat;
baseData.modifyLon = currentLon;
const locationField = document.getElementById('location_field');
if (locationField) {
locationField.textContent = `Lat: ${currentLat.toFixed(4)}, Lon: ${currentLon.toFixed(4)}`;
}
});
}
}
/**
* 팝업 레이어를 엽니다.
*/
function openPopup(element) {
const targetId = element.getAttribute('to');
const popup = document.querySelector(targetId);
const overlay = document.querySelector('.dim_layer');
if (popup && overlay) {
overlay.style.display = 'block';
popup.style.display = 'block';
}
}
/**
* 팝업 레이어를 닫습니다.
*/
function closePopup() {
const overlay = document.querySelector('.dim_layer');
if(overlay) overlay.style.display = 'none';
document.querySelectorAll('.pop_layer').forEach(p => p.style.display = 'none');
}
/**
* 게시물 상세 보기 페이지로 이동합니다.
*/
function goToViewer(element) {
if (element && element.id) {
location.href = `${getMainPath()}/blog/viewer/${element.id}`;
}
}
// =================================================================================
// [복구] 이하 누락되었던 함수들
// =================================================================================
/**
* 인기글 목록을 가져와 UI에 표시합니다.
*/
function fetchRankOfViews() {
fetch(`${getMainPath()}/blog/rankOfViews.bjx`).then(res => res.json()).then(data => {
const ul = document.querySelector('.rank_of_view');
if (ul && data.posts) {
ul.innerHTML = '';
data.posts.forEach(item => {
const date = new Date(item.writeTime);
const formattedDate = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
ul.innerHTML += `<li><a href="${getMainPath()}/blog/viewer/${item.id}">${item.title}<br>[${formattedDate}]</a></li>`;
});
}
}).catch(error => console.error('Failed to fetch rank of views:', error));
}
/**
* 최신글 목록을 가져와 UI에 표시합니다.
*/
function fetchRecentPosts() {
fetch(`${getMainPath()}/blog/recentOfPost.bjx`).then(res => res.json()).then(data => {
const ul = document.querySelector('.recent_posts');
if (ul && data.posts) {
ul.innerHTML = '';
data.posts.forEach(item => {
const date = new Date(item.writeTime);
const formattedDate = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
ul.innerHTML += `<li><a href="${getMainPath()}/blog/viewer/${item.id}">${item.title}<br>[${formattedDate}]</a></li>`;
});
}
}).catch(error => console.error('Failed to fetch recent posts:', error));
}
/**
* 로그인 데이터를 서버에 전송합니다.
*/
function submitLoginForm() {
const data = {
'user_id': $('#loginId').val(),
'user_pw': $('#loginPassword').val(),
'rememberMe': $('#rememberMe').is(':checked'),
};
postLogin(`${getMainPath()}/user/login.bjx`, serverData.enc, JSON.stringify(data), serverData.keyword, function(response) {
if (response.isOk) {
location.reload();
} else {
alert(`로그인 실패: ${response.resultMsg}`);
}
});
}
// --- 페이지 이동(Navigation) 함수들 ---
function gotoHome() { document.location.replace(`${getMainPath()}/home.bs`); }
function gotoWrite() { document.location.replace(`${getMainPath()}/blog/edit`); } // 수정된 URL
function gotoModify() { document.location.replace(`${getMainPath()}/blog/posts`); } // 수정된 URL
function gotoLogin() { document.location.replace(`${getMainPath()}/login.bs`); }
function gotoJoin() { document.location.replace(`${getMainPath()}/user/join.bs`); }
/**
* 로그아웃을 처리합니다.
*/
function logout() {
// retrieve all cookies
document.cookie = "access=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
console.log(document.cookie["JSESSIONID"])
document.cookie = "JSESSIONID=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
document.cookie = "CLEAR="+Date.now()+"";
let logOutUrl = getMainPath() + "/user/logout.bs";
alert("로그아웃 됨요~! 빠염~!")
// 동적으로 form 생성하여 POST 요청 전송
const form = document.createElement('form');
form.method = 'POST';
form.action = getMainPath() + '/user/logout.bs';
form.action = `${getMainPath()}/user/logout.bs`;
// CSRF 토큰을 meta태그 등에서 얻어서 삽입 (예: <meta name="_csrf" content="토큰값">)
// Spring Security CSRF 토큰 추가
const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const csrfParam = document.querySelector('meta[name="_csrf_parameter"]').getAttribute('content');
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrfParam; // 예: "_csrf"
csrfInput.value = csrfToken;
form.appendChild(csrfInput);
if (csrfToken && csrfParam) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrfParam;
csrfInput.value = csrfToken;
form.appendChild(csrfInput);
}
document.body.appendChild(form);
form.submit();
}
function gotoHome() {
console.log(`location.port >> ${location.port}`)
location.href = getMainPath()+"/home.bs"
}
function gotoLogin() {
console.log(`location.port >> ${location.port}`)
location.href = getMainPath()+"/login.bs"
}
function gotoJoin() {
document.location.replace(getMainPath() + "/user/join.bs")
}
function goToViewer(item) {
location.href = getMainPath() + "/blog/viewer/" + item.id
}
function goToView(path,id) {
location.href = path + id;
}
function onclickLogin(type, keyword) {
let user_id = document.getElementById('user_id')
let user_pw = document.getElementById('user_pw')
let data = {
'user_id': user_id.value,
'user_pw': user_pw.value,
}
postLogin(getMainPath()+"/user/login.bjx",type,JSON.stringify(data),keyword, function (data) {
if (data.isOk) {
document.cookie = "access=" + data.token.split(";")[0]+";"
// document.cookie = "refresh=" + data.refresh.split(";")[0]+";"
// window.sessionStorage.setItem("ACCESS",data.refresh.split(";")[0])
window.sessionStorage.setItem("REFRESH",data.refresh.split(";")[0])
document.location.replace(document.location)
} else {
if (data.resultCode === 7100) {
if(confirm(`너 누구임 정보 없는데?!\n${data.resultMsg}[${data.resultCode}]\n가입 할래!?`)){
document.location.replace(getMainPath() + "/user/join.bs")
}
} else {
alert(`너 누구임?!\n${data.resultMsg}[${data.resultCode}]`)
}
}
})
}
// =================================================================================
// 서버 통신 및 암호화 관련 유틸리티 함수들 (기존 코드 유지)
// =================================================================================
function getMainPath() {
console.log(`location.port >> ${location.port}`)
if ('443' === location.port) {
return location.protocol + "//" + location.hostname + ":" + location.port
} else {
return location.protocol + "//" + location.hostname
}
return location.protocol + "//" + location.hostname + (location.port ? ':' + location.port : '');
}
function openPopup(a) {
var $href = $(a).attr('to');
document.querySelectorAll('[id*=popLayer]').forEach(function (v,k,p) {
$(v).hide();
});
layer_popup($href);
}
function layer_popup(el){
var $el = $(el); //레이어의 id를 $el 변수에 저장
var isDim = true ;//$(document).hasClass('dimBg'); //dimmed 레이어를 감지하기 위한 boolean 변수
isDim ? $('.dim_layer').fadeIn() : $el.fadeIn();
$el.show()
var $elWidth = ~~($el.outerWidth()),
$elHeight = ~~($el.outerHeight()),
docWidth = $(document).width(),
docHeight = $(document).height();
// 화면의 중앙에 레이어를 띄운다.
if ($elHeight < docHeight || $elWidth < docWidth) {
$el.css({
marginTop: -$elHeight /2,
marginLeft: -$elWidth/2
})
} else {
$el.css({top: 0, left: 0});
}
$el.find('a.btn_layerClose').click(function(){
isDim ? $('.dim_layer').fadeOut() : $el.fadeOut(); // 닫기 버튼을 클릭하면 레이어가 닫힌다.
return false;
});
$('.dimBg').click(function(){
$('.dim_layer').fadeOut();
return false;
});
}
function urldecode(t){
return decodeURI(t)
}
function openLoginPopup(formType) {
document.getElementById('overlay').style.display = 'block';
document.getElementById('loginForm').style.display = formType === 'login' ? 'block' : 'none';
document.getElementById('signupForm').style.display = formType === 'signup' ? 'block' : 'none';
if(formType === 'login') {
const loginIdInput = document.getElementById('loginId');
loginIdInput.focus();
}
}
function closePopup() {
document.getElementById('overlay').style.display = 'none';
}
function submitLoginForm() {
let user_id = document.getElementById('loginId')
let user_pw = document.getElementById('loginPassword')
let rememberMe = document.getElementById('rememberMe')
console.log(rememberMe.value)
let data = {
'user_id': user_id.value,
'user_pw': user_pw.value,
'rememberMe' : rememberMe.value === "on",
}
postLogin(getMainPath()+"/user/login.bjx",user_pw.data,JSON.stringify(data),user_pw.data, function (data) {
closePopup()
alert("data >> " + data)
if (data.isOk) {
document.location.replace(location.href)
} else {
if (data.resultCode === 7100) {
if(confirm(`너 누구임 정보 없는데?!\n${data.resultMsg}[${data.resultCode}]\n가입 할래!?`)){
document.location.replace(getMainPath() + "/user/join")
}
} else {
alert(`너 누구임?!\n${data.resultMsg}[${data.resultCode}]`)
}
function post(target, type, data, key, callBackResult) {
const httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) callBackResult(httpRequest.response);
else alert('Request Error!');
}
})
};
httpRequest.open('POST', target, true);
httpRequest.setRequestHeader("Content-Type", "text/plain");
httpRequest.send(btoa(JSON.stringify({
'data': unformat(type, data, key), 'key': key, 'type': type,
})));
}
function isDelta(content) {
try {
// Delta는 JSON이면서 'ops'라는 키를 포함
if (typeof content === "string") {
content = JSON.parse(content);
function postLogin(target, type, data, key, callBackResult) {
const httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
try {
callBackResult(JSON.parse(httpRequest.response));
} catch (e) { console.error("Login response parse error:", e); }
} else { alert('Request Error!'); }
}
return typeof content === "object" && content.ops !== undefined;
} catch (e) {
return false; // JSON 파싱 실패하면 마크업(HTML)으로 간주
};
httpRequest.withCredentials = true;
httpRequest.open('POST', target, true);
httpRequest.setRequestHeader("Content-Type", "text/plain");
httpRequest.send(btoa(JSON.stringify({
'data': unformat(type, data, key), 'key': key, 'type': type,
})));
}
function unformat(type, data, key) {
var even = [], odd = [];
data.split("").forEach((v, idx) => (idx % 2 === 0 ? even.push(v) : odd.push(v)));
const dividerStr = ["%7C%2A-%2A%7C", key, "%7C%2A-%2A%7C"].join("");
switch (type) {
case "T0":
return [odd.join(""), dividerStr, even.join("")].join("");
case "T1":
return [odd.reverse().join(""), dividerStr, even.join("")].join("");
case "T2":
return [odd.join(""), dividerStr, even.reverse().join("")].join("");
default:
return [odd.reverse().join(""), dividerStr, even.reverse().join("")].join("");
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,342 @@
/*
* Arcana by HTML5 UP (html5up.net | @ajlkn)
* Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*
* 파일은 테마의 유틸리티 스크립트를 포함합니다. (패널, پلی필 )
*/
(function($) {
/**
* 네비게이션 메뉴(ul) 모바일 패널에서 사용할 있는 평면 링크 목록으로 변환합니다.
* @return {jQuery} jQuery 객체
*/
$.fn.navList = function() {
var $this = $(this),
$a = $this.find('a'),
b = [];
$a.each(function() {
var $this = $(this),
indent = Math.max(0, $this.parents('li').length - 1),
href = $this.attr('href'),
target = $this.attr('target'),
// [수정된 부분] 팝업을 열기 위해 원본 링크의 class와 'to' 속성을 가져옵니다.
originalClass = $this.attr('class'),
toAttr = $this.attr('to');
// [수정된 부분] 모바일 패널용 링크에 기본 클래스와 원본 클래스를 모두 포함시킵니다.
var classes = 'link depth-' + indent;
if (typeof originalClass !== 'undefined' && originalClass != '') {
classes += ' ' + originalClass;
}
b.push(
'<a ' +
'class="' + classes + '"' + // 수정된 클래스 적용
( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
// [수정된 부분] 팝업 대상을 지정하는 'to' 속성을 추가합니다.
( (typeof toAttr !== 'undefined' && toAttr != '') ? ' to="' + toAttr + '"' : '') +
'>' +
'<span class="indent-' + indent + '"></span>' +
$this.text() +
'</a>'
);
});
return b.join('');
};
/**
* 특정 요소를 슬라이드 아웃 패널로 변환합니다.
* @param {object} userConfig 사용자 설정 객체
* @return {jQuery} jQuery 객체
*/
$.fn.panel = function(userConfig) {
// 요소가 없으면 반환
if (this.length == 0)
return $this;
// 여러 요소에 적용될 경우, 각각에 대해 재귀적으로 호출
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i]).panel(userConfig);
return $this;
}
// 변수 설정
var $this = $(this),
$body = $('body'),
$window = $(window),
id = $this.attr('id'),
config;
// 기본 설정과 사용자 설정을 병합
config = $.extend({
delay: 0, // 지연 시간
hideOnClick: false, // 링크 클릭 시 패널 숨김 여부
hideOnEscape: false, // ESC 키 누를 시 패널 숨김 여부
hideOnSwipe: false, // 스와이프 시 패널 숨김 여부
resetScroll: false, // 숨길 때 스크롤 리셋 여부
resetForms: false, // 숨길 때 폼 리셋 여부
side: null, // 패널이 나타날 위치 (top, bottom, left, right)
target: $this, // 패널이 보일 때 클래스가 적용될 대상
visibleClass: 'visible' // 패널이 보일 때 적용될 클래스 이름
}, userConfig);
// 패널 숨기기 내부 함수
$this._hide = function(event) {
if (!config.target.hasClass(config.visibleClass))
return;
if (event) {
event.preventDefault();
event.stopPropagation();
}
config.target.removeClass(config.visibleClass);
window.setTimeout(function() {
if (config.resetScroll)
$this.scrollTop(0);
if (config.resetForms)
$this.find('form').each(function() {
this.reset();
});
}, config.delay);
};
// 브라우저 호환성을 위한 CSS 설정
$this
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
.css('-webkit-overflow-scrolling', 'touch');
// 링크 클릭 시 패널 숨기기 이벤트 핸들러
if (config.hideOnClick) {
$this.find('a')
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
$this
.on('click', 'a', function(event) {
var $a = $(this),
href = $a.attr('href'),
target = $a.attr('target');
// --- ⬇️ 수정된 로직 시작 ⬇️ ---
// 1. 팝업 링크인지 먼저 확인합니다.
// 링크에 'open-login-popup' 클래스가 있으면 메뉴를 닫고, 이 핸들러의 동작은 여기서 종료합니다.
// (팝업을 여는 동작은 common.js에 있는 다른 이벤트 핸들러가 처리합니다.)
if ($a.hasClass('open-login-popup')) {
$this._hide();
return;
}
// 2. 기존의 일반 링크(페이지 이동) 처리 로직은 그대로 둡니다.
if (!href || href == '#' || href == '' || href == '#' + id)
return;
// --- ⬆️ 수정된 로직 끝 ⬆️ ---
// Cancel original event.
event.preventDefault();
event.stopPropagation();
// Hide panel.
$this._hide();
// Redirect to href.
window.setTimeout(function() {
if (target == '_blank')
window.open(href);
else
window.location.href = href;
}, config.delay + 10);
});
}
// 터치 및 스와이프 이벤트 핸들러
$this.on('touchstart', function(event) {
$this.touchPosX = event.originalEvent.touches[0].pageX;
$this.touchPosY = event.originalEvent.touches[0].pageY;
});
$this.on('touchmove', function(event) {
if ($this.touchPosX === null || $this.touchPosY === null) return;
var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
th = $this.outerHeight(),
ts = ($this.get(0).scrollHeight - $this.scrollTop());
if (config.hideOnSwipe) {
var result = false, boundary = 20, delta = 50;
switch (config.side) {
case 'left': result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); break;
case 'right': result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); break;
case 'top': result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); break;
case 'bottom': result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); break;
default: break;
}
if (result) {
$this.touchPosX = null;
$this.touchPosY = null;
$this._hide();
return false;
}
}
if (($this.scrollTop() < 0 && diffY < 0) || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
event.preventDefault();
event.stopPropagation();
}
});
// 패널 내부에서 발생하는 이벤트가 상위로 전파되는 것을 방지
$this.on('click touchend touchstart touchmove', function(event) {
event.stopPropagation();
});
// 패널 ID를 가리키는 링크 클릭 시 패널 숨기기
$this.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.removeClass(config.visibleClass);
});
// body 클릭 시 패널 숨기기
$body.on('click touchend', function(event) {
$this._hide(event);
});
// 패널을 여는 링크(토글)에 대한 이벤트 핸들러
$body.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.toggleClass(config.visibleClass);
});
// ESC 키 누를 시 패널 숨기기
if (config.hideOnEscape) {
$window.on('keydown', function(event) {
if (event.keyCode == 27) $this._hide(event);
});
}
return $this;
};
/**
* 구형 브라우저에서 input의 'placeholder' 속성을 지원하기 위한 پلی필(Polyfill)입니다.
* @return {jQuery} jQuery 객체
*/
$.fn.placeholder = function() {
if (typeof (document.createElement('input')).placeholder != 'undefined')
return $(this);
if (this.length == 0) return $this;
if (this.length > 1) {
for (var i=0; i < this.length; i++) $(this[i]).placeholder();
return $this;
}
var $this = $(this);
$this.find('input[type=text],textarea').each(function() {
var i = $(this);
if (i.val() == '' || i.val() == i.attr('placeholder'))
i.addClass('polyfill-placeholder').val(i.attr('placeholder'));
}).on('blur', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/)) return;
if (i.val() == '')
i.addClass('polyfill-placeholder').val(i.attr('placeholder'));
}).on('focus', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/)) return;
if (i.val() == i.attr('placeholder'))
i.removeClass('polyfill-placeholder').val('');
});
$this.find('input[type=password]').each(function() {
var i = $(this);
var x = $($('<div>').append(i.clone()).remove().html().replace(/type="password"/i, 'type="text"').replace(/type=password/i, 'type=text'));
if (i.attr('id') != '') x.attr('id', i.attr('id') + '-polyfill-field');
if (i.attr('name') != '') x.attr('name', i.attr('name') + '-polyfill-field');
x.addClass('polyfill-placeholder').val(x.attr('placeholder')).insertAfter(i);
if (i.val() == '') i.hide(); else x.hide();
i.on('blur', function(event) {
event.preventDefault();
var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') { i.hide(); x.show(); }
});
x.on('focus', function(event) {
event.preventDefault();
var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
x.hide();
i.show().focus();
}).on('keypress', function(event) {
event.preventDefault();
x.val('');
});
});
$this.on('submit', function() {
$this.find('input[type=text],input[type=password],textarea').each(function(event) {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/)) i.attr('name', '');
if (i.val() == i.attr('placeholder')) {
i.removeClass('polyfill-placeholder');
i.val('');
}
});
}).on('reset', function(event) {
event.preventDefault();
$this.find('select').val($('option:first').val());
$this.find('input,textarea').each(function() {
var i = $(this), x;
i.removeClass('polyfill-placeholder');
switch (this.type) {
case 'submit': case 'reset': break;
case 'password':
i.val(i.attr('defaultValue'));
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') { i.hide(); x.show(); } else { i.show(); x.hide(); }
break;
case 'checkbox': case 'radio': i.attr('checked', i.attr('defaultValue')); break;
case 'text': case 'textarea':
i.val(i.attr('defaultValue'));
if (i.val() == '') { i.addClass('polyfill-placeholder'); i.val(i.attr('placeholder')); }
break;
default: i.val(i.attr('defaultValue')); break;
}
});
});
return $this;
};
/**
* 특정 조건에 따라 요소의 순서를 부모 요소의 앞으로 이동시키거나 원래 위치로 되돌립니다.
* (주로 반응형 레이아웃에서 요소의 위치를 변경할 사용됩니다.)
* @param {jQuery} $elements 이동시킬 요소
* @param {bool} condition true이면 앞으로, false이면 원래 위치로 이동
*/
$.prioritize = function($elements, condition) {
var key = '__prioritize';
if (typeof $elements != 'jQuery') $elements = $($elements);
$elements.each(function() {
var $e = $(this), $p, $parent = $e.parent();
if ($parent.length == 0) return;
if (!$e.data(key)) {
if (!condition) return;
$p = $e.prev();
if ($p.length == 0) return;
$e.prependTo($parent);
$e.data(key, $p);
} else {
if (condition) return;
$p = $e.data(key);
$e.insertAfter($p);
$e.removeData(key);
}
});
};
})(jQuery);

View File

Before

Width:  |  Height:  |  Size: 730 KiB

After

Width:  |  Height:  |  Size: 730 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 898 KiB

After

Width:  |  Height:  |  Size: 898 KiB

View File

@ -3,88 +3,92 @@
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}"
>
<th:block layout:fragment="head" id="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<!-- Quill 스타일 시트 -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<head>
<th:block layout:fragment="head" id="head">
<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>
<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-table-better@1/dist/quill-table-better.js"></script>
<script>document.addEventListener('DOMContentLoaded', function() {initEditor(true)});</script>
</th:block>
</head>
<!-- Quill 라이브러리 -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<!-- HTML 에디터 영역 -->
<!-- <div id="editor-container" style="height: 300px;"></div>-->
<!-- Quill 라이브러리 및 테마 -->
<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>
<!-- 테이블 플러그인 CSS & JS -->
<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-table-better@1/dist/quill-table-better.js"></script>
<script>document.addEventListener('DOMContentLoaded', function() {initEditor(true)});</script>
</th:block>
<body>
<th:block layout:fragment="content" id="content">
<section class="wrapper style2" >
<th:block sec:authorize="isAuthenticated()">
<div class="container" >
<header class="major">
<h3><label for="title_field"><input id="title_field" th:value="${srcPost.title}" style="width:100%; max-width:100%; box-sizing:border-box;
border:none; outline:none; text-align:center;"></label></h3>
<p th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')}"></p>
<h2 th:text="${pageTitle}">글 작성/수정</h2>
<h3>
<label for="title_field">
<input id="title_field" th:value="${srcPost.title}" style="width:100%; max-width:100%; box-sizing:border-box; border:none; outline:none; text-align:center;">
</label>
</h3>
<p th:if="${srcPost.writeTime != null and srcPost.writeTime > 0}"
th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')}"></p>
</header>
</div>
</th:block>
</section>
<section class="wrapper style2">
<section class="wrapper style1">
<th:block sec:authorize="isAnonymous()">
<h1>권한이 없는 뎁쇼?!</h1>
<div class="container"><h1>권한이 없는 뎁쇼?!</h1></div>
</th:block>
<th:block sec:authorize="isAuthenticated()">
<div id="editor" ></div>
<div class="write_controllbox">
<div class="write_option btn-example" to="#popLayer1" onclick="openPopup(this)" ></div>
<div class="write_option btn-example" to="#popLayer2" onclick="openPopup(this)" id="hashtag_field"></div>
<label for="location_field"></label><input class="write_option" readonly id="location_field"/>
<th:block sec:authorize="isAuthenticated()">
<div class="container">
<div id="editor"></div>
<div class="write_controllbox">
<div class="write_option btn-example controlbox-category" to="#popLayer1">
</div>
<div style="width: 15px"></div>
<div class="write_option btn-example controlbox-hashtag" to="#popLayer2" id="hashtag_field">
</div>
<div style="width: 15px"></div>
<div class="write_option" id="location_field"></div>
</div>
<button id="save" class="button fit" style="margin-top: 1em;" onclick="save()">저장하기</button>
</div>
<h1><button id="save" class="write_option" style="width: 100%; position: relative" onclick="save()">저장하셈</button></h1>
</th:block>
</section>
</th:block>
<th:block layout:fragment="popup_layer">
<div id="popLayer1" class="pop_layer">
<div id="popLayer1" class="pop_layer category-popup">
<div class="pop_container">
<div class="pop_conts">
<!--content //-->
<p class="ctxt mb20">Thank you.<br>
Your registration was submitted successfully.<br>
Selected invitees will be notified by e-mail on JANUARY 24th.<br><br>
Hope to see you soon!
</p>
<h2>Categories</h2>
<div id="category-list" class="tag-list"></div>
<input type="text" id="category-input" placeholder="Add a new category" class="tag-input">
<button id="add-category-btn" class="button">Add</button>
<div class="btn_r">
<a href="#" class="btn_layerClose">Close</a>
</div>
<!--// content-->
</div>
</div>
</div>
<div id="popLayer2" class="pop_layer">
<div id="popLayer2" class="pop_layer hashtag-popup">
<div class="pop_container">
<div class="pop_conts">
<!--content //-->
<p class="ctxt mb20">
test 002
</p>
<h2>Hashtags</h2>
<div id="hashtag-list" class="tag-list"></div>
<input type="text" id="hashtag-input" placeholder="Add a new hashtag" class="tag-input">
<button id="add-hashtag-btn" class="button">Add</button>
<div class="btn_r">
<a href="#" class="btn_layerClose">Close</a>
</div>
<!--// content-->
</div>
</div>
</div>
</th:block>
</html>
</body>
</html>

View File

@ -5,8 +5,6 @@
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
</th:block>
<th:block layout:fragment="content" id="content">

View File

@ -5,102 +5,39 @@
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}"
>
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/toast-ui-view.js}"></script>
<link th:href="@{/css/toast-ui-dark.css}" rel="stylesheet" />
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
let editor
let onChange = () => {console.log(editor.getMarkdown())}
document.addEventListener("DOMContentLoaded", onLoaded);
function onLoaded() {
}
</script>
</th:block>
<head>
<th:block layout:fragment="head">
</th:block>
</head>
<body>
<th:block layout:fragment="content" id="content">
<section class="wrapper style1">
<div class="container">
<div class="row gtr-200">
<!-- <div class="col-3 col-12-narrower">-->
<!-- <div id="sidebar1">-->
<!-- <section>-->
<!-- <h3>Just a Subheading</h3>-->
<!-- <p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.-->
<!-- Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.-->
<!-- Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>-->
<!-- <footer>-->
<!-- <a href="#" class="button">Continue Reading</a>-->
<!-- </footer>-->
<!-- </section>-->
<!-- <section>-->
<!-- <h3>Another Subheading</h3>-->
<!-- <ul class="links">-->
<!-- <li><a href="#">Amet turpis, feugiat sit amet</a></li>-->
<!-- <li><a href="#">Ornare in hendrerit lectus</a></li>-->
<!-- <li><a href="#">Semper mod quis eget dolore</a></li>-->
<!-- <li><a href="#">Consequat lorem phasellus</a></li>-->
<!-- <li><a href="#">Amet turpis feugiat amet</a></li>-->
<!-- <li><a href="#">Semper mod quisturpis nisi</a></li>-->
<!-- </ul>-->
<!-- <footer>-->
<!-- <a href="#" class="button">More Random Links</a>-->
<!-- </footer>-->
<!-- </section>-->
<!-- </div>-->
<!-- </div>-->
<div class="col-6 col-12-narrower imp-narrower">
<div class="row justify-content-center">
<div class="col-8 col-12-narrower">
<div id="content">
<article>
<section class="col-6 col-12-narrower imp-narrower" th:each="post : ${Posts}">
<!-- <span th:text="${cell}"></span>-->
<div class="box post" onclick="goToViewer(this)" th:id="${post.id}">
<a href="javascript:goToViewer(this)" th:id="${post.id}" th:data="${post.id}" class="image left"><img th:src="${#strings.length(post.thumb) > 0} ? ${post.thumb} : 'images/pic01.jpg'" alt="" /></a>
<section th:each="post : ${Posts}">
<div class="box post" th:id="${post.id}">
<a href="javascript:void(0);" th:onclick="'goToViewer(this.parentNode)'" class="image left">
<img th:src="${post.thumb != null and not #strings.isEmpty(post.thumb)} ? @{${post.thumb}} : @{/images/pic01.jpg}" alt="Post Thumbnail" />
</a>
<div class="inner">
<h3 th:text="${#strings.length(post.title) > 0} ? ${post.title} : ('untitled[' + ${#temporals.format(T(java.time.Instant).ofEpochMilli(post.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')} + ']')"></h3>
<h3 th:text="${post.title != null and not #strings.isEmpty(post.title)} ? ${post.title} : 'untitled[' + ${#temporals.format(T(java.time.Instant).ofEpochMilli(post.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm')} + ']'"></h3>
<p th:text="${#strings.abbreviate(post.html, 80)}" class="ellipsis"></p>
</div>
<footer sec:authorize="isAuthenticated()" style="text-align: right; margin-top: 1em;">
<a th:href="@{/blog/edit/{postId}(postId=${post.id})}" class="button small alt">수정</a>
</footer>
</div>
</section>
</article>
</div>
</div>
<!-- <div class="col-3 col-12-narrower">-->
<!-- <div id="sidebar2">-->
<!-- <section>-->
<!-- <h3>Another Subheading</h3>-->
<!-- <ul class="links">-->
<!-- <li><a href="#">Amet turpis, feugiat sit amet</a></li>-->
<!-- <li><a href="#">Ornare in hendrerit lectus</a></li>-->
<!-- <li><a href="#">Semper mod quis eget dolore</a></li>-->
<!-- <li><a href="#">Consequat lorem phasellus</a></li>-->
<!-- <li><a href="#">Amet turpis feugiat amet</a></li>-->
<!-- <li><a href="#">Semper mod quisturpis nisi</a></li>-->
<!-- </ul>-->
<!-- <footer>-->
<!-- <a href="#" class="button">More Random Links</a>-->
<!-- </footer>-->
<!-- </section>-->
<!-- <section>-->
<!-- <h3>Just a Subheading</h3>-->
<!-- <p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.-->
<!-- Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.-->
<!-- Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>-->
<!-- <footer>-->
<!-- <a href="#" class="button">Continue Reading</a>-->
<!-- </footer>-->
<!-- </section>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</section>
</th:block>
</html>
</body>
</html>

View File

@ -5,57 +5,67 @@
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}"
>
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<!-- Quill 스타일 시트 -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<head>
<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)});</script>
<!-- Quill 라이브러리 -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<!-- HTML 에디터 영역 -->
<!-- <div id="editor-container" style="height: 300px;"></div>-->
<!-- Quill 라이브러리 및 테마 -->
<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>
<!-- 테이블 플러그인 CSS & JS -->
<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-table-better@1/dist/quill-table-better.js"></script>
<script>document.addEventListener('DOMContentLoaded', function() {initEditor()});</script>
</th:block>
</th:block>
</head>
<body>
<th:block layout:fragment="content" id="content">
<section class="wrapper style2">
<div class="container" sec:authorize="isAuthenticated()" onclick="loadEditor()">
<div class="container" sec:authorize="isAuthenticated()" onclick="loadEditor()" style="cursor: pointer;" title="클릭하여 수정하기">
<header class="major">
<h2 id="title_layer" th:text="${srcPost.title}">A gigantic heading you can use for whatever</h2>
<h2 id="title_layer" th:text="${srcPost.title}">게시물 제목이 여기에 표시됩니다</h2>
<p th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')}"></p>
</header>
</div>
<div class="container" sec:authorize="isAnonymous()" onclick="openLoginPopup('login')">
<div class="container open-login-popup" sec:authorize="isAnonymous()" to="#loginPopup" style="cursor: pointer;">
<header class="major">
<h2 id="title_layer" th:text="${srcPost.title}">A gigantic heading you can use for whatever</h2>
<h2 id="title_layer" th:text="${srcPost.title}">게시물 제목이 여기에 표시됩니다</h2>
<p th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')}"></p>
</header>
</div>
</section>
<section class="wrapper style1">
<div class="container">
<div id="content">
<article>
<div id="editor" ></div>
<div id="editor"></div>
<div class="write_controllbox">
<div class="write_option btn-example" to="#popLayer1" onclick="openPopup(this)" ></div>
<div style="width: 15px" ></div>
<div class="write_option btn-example" to="#popLayer2" onclick="openPopup(this)" id="hashtag_field"></div>
<div style="width: 15px" ></div>
<div class="write_option" id="location_field" ></div>
<div class="write_option controlbox-category">
</div>
<div style="width: 15px"></div>
<div class="write_option controlbox-hashtag" id="hashtag_field">
</div>
<div style="width: 15px"></div>
<div class="write_option" id="location_field">
</div>
</div>
<h3 id="write" th:text="${srcPost.firstAddress}"></h3>
<h3 id="modify" th:text="${srcPost.modifyAddress}"></h3>
</article>
<section class="comment-section">
<h2>Comments</h2>
<div id="comment-form-container">
<textarea id="comment-input" placeholder="댓글을 입력하세요..."></textarea>
<button id="submit-comment" class="button">등록</button>
</div>
<div id="comments-list">
</div>
</section>
</div>
</div>
</section>
</th:block>
</html>
</body>
</html>

View File

@ -1,88 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}"
>
<th:block layout:fragment="head" id="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<!-- Quill 스타일 시트 -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<!-- Quill 라이브러리 -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<!-- HTML 에디터 영역 -->
<!-- <div id="editor-container" style="height: 300px;"></div>-->
<!-- Quill 라이브러리 및 테마 -->
<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>
<!-- 테이블 플러그인 CSS & JS -->
<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-table-better@1/dist/quill-table-better.js"></script>
<script>document.addEventListener('DOMContentLoaded', function() {initEditor(true)});</script>
</th:block>
<th:block layout:fragment="content" id="content">
<div id="main_layer">
<section class="wrapper style1">
<div class="container">
<div id="content">
<article>
<th:block sec:authorize="isAnonymous()">
<h1>권한이 없는 뎁쇼?!</h1>
</th:block>
<th:block sec:authorize="isAuthenticated()">
<div class="layer">
<input id="title_field" class="write_option" />
</div>
<div id="editor" ></div>
<div class="write_controllbox">
<div class="write_option btn-example" to="#popLayer1" onclick="openPopup(this)" ></div>
<div style="width: 15px" ></div>
<div class="write_option btn-example" to="#popLayer2" onclick="openPopup(this)" id="hashtag_field"></div>
<div style="width: 15px" ></div>
<label class="write_option" readonly id="location_field"></label>
</div>
<h1><button id="save" class="write_option" style="width: 100%; position: relative" onclick="save()">저장하셈</button></h1>
</th:block>
</article>
</div>
</div>
</section>
</div>
</th:block>
<th:block layout:fragment="popup_layer">
<div id="popLayer1" class="pop_layer">
<div class="pop_container">
<div class="pop_conts">
<!--content //-->
<p class="ctxt mb20">Thank you.<br>
Your registration was submitted successfully.<br>
Selected invitees will be notified by e-mail on JANUARY 24th.<br><br>
Hope to see you soon!
</p>
<div class="btn_r">
<a href="#" class="btn_layerClose">Close</a>
</div>
<!--// content-->
</div>
</div>
</div>
<div id="popLayer2" class="pop_layer">
<div class="pop_container">
<div class="pop_conts">
<!--content //-->
<p class="ctxt mb20">
test 002
</p>
<div class="btn_r">
<a href="#" class="btn_layerClose">Close</a>
</div>
<!--// content-->
</div>
</div>
</div>
</th:block>
</html>

View File

@ -4,13 +4,6 @@
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
</script>
</th:block>
<th:block layout:fragment="content" id="content">
<section id="banner">
<header>
@ -67,12 +60,13 @@
</div>
</section>
<section class="col-4 col-12-narrower">
<div class="box highlight" sec:authorize="isAuthenticated()" onclick=gotoWrite()>
<i class="icon solid major fa-pencil-alt"></i>
<h3>글쓰기[Writing]</h3>
<p>오직 주인장 만의 권한 임요. 그냥 내가 쓰기 편하게 여기 놔둔 메뉴임. 님들은 못씀요.<br>[Only the owner has the authority. This is just a menu that I put here for my convenience. You can't use it.]</p>
</div>
<div class="box highlight" sec:authorize="isAnonymous()" onclick="openLoginPopup('login')">
<div class="box highlight open-login-popup" sec:authorize="isAnonymous()" to="#loginPopup" style="cursor: pointer;">
<i class="icon solid major fa-pencil-alt"></i>
<h3>글쓰기[Writing]</h3>
<p>오직 주인장 만의 권한 임요. 그냥 내가 쓰기 편하게 여기 놔둔 메뉴임. 님들은 못씀요.<br>[Only the owner has the authority. This is just a menu that I put here for my convenience. You can't use it.]</p>

View File

@ -9,7 +9,6 @@
<title>BUM'sPace</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body class="is-preload">
<div id="page-wrapper">

View File

@ -1,94 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
document.addEventListener("DOMContentLoaded", onLoaded);
</script>
</th:block>
<th:block layout:fragment="content" id="content">
<!-- Main -->
<section class="wrapper style1">
<div class="container">
<div class="row gtr-200">
<div class="col-4 col-12-narrower">
<div id="sidebar">
<!-- Sidebar -->
<section>
<h3>Just a Subheading</h3>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>
<footer>
<a href="#" class="button">Continue Reading</a>
</footer>
</section>
<section>
<h3>Another Subheading</h3>
<ul class="links">
<li><a href="#">Amet turpis, feugiat et sit amet</a></li>
<li><a href="#">Ornare in hendrerit in lectus</a></li>
<li><a href="#">Semper mod quis eget mi dolore</a></li>
<li><a href="#">Consequat etiam lorem phasellus</a></li>
<li><a href="#">Amet turpis, feugiat et sit amet</a></li>
<li><a href="#">Semper mod quisturpis nisi</a></li>
</ul>
<footer>
<a href="#" class="button">More Random Links</a>
</footer>
</section>
</div>
</div>
<div class="col-8 col-12-narrower imp-narrower">
<div id="content">
<!-- Content -->
<article>
<header>
<h2>Left Sidebar</h2>
<p>Sidebar on the left, content on the right.</p>
</header>
<span class="image featured"><img src="images/banner.jpg" alt="" /></span>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis nisi
consequat etiam lorem ipsum dolor sit amet nullam.</p>
<h3>And Yet Another Subheading</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac quam risus, at tempus
justo. Sed dictum rutrum massa eu volutpat. Quisque vitae hendrerit sem. Pellentesque lorem felis,
ultricies a bibendum id, bibendum sit amet nisl. Mauris et lorem quam. Maecenas rutrum imperdiet
vulputate. Nulla quis nibh ipsum, sed egestas justo. Morbi ut ante mattis orci convallis tempor.
Etiam a lacus a lacus pharetra porttitor quis accumsan odio. Sed vel euismod nisi. Etiam convallis
rhoncus dui quis euismod. Maecenas lorem tellus, congue et condimentum ac, ullamcorper non sapien.
Donec sagittis massa et leo semper a scelerisque metus faucibus. Morbi congue mattis mi.
Phasellus sed nisl vitae risus tristique volutpat. Cras rutrum commodo luctus.</p>
<p>Phasellus odio risus, faucibus et viverra vitae, eleifend ac purus. Praesent mattis, enim
quis hendrerit porttitor, sapien tortor viverra magna, sit amet rhoncus nisl lacus nec arcu.
Suspendisse laoreet metus ut metus imperdiet interdum aliquam justo tincidunt. Mauris dolor urna,
fringilla vel malesuada ac, dignissim eu mi. Praesent mollis massa ac nulla pretium pretium.
Maecenas tortor mauris, consectetur pellentesque dapibus eget, tincidunt vitae arcu.
Vestibulum purus augue, tincidunt sit amet iaculis id, porta eu purus.</p>
</article>
</div>
</div>
</div>
</div>
</section>
</th:block>
</html>

View File

@ -1,58 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
document.addEventListener("DOMContentLoaded", onLoaded);
</script>
</th:block>
<th:block layout:fragment="content" id="content">
<!-- Main -->
<section class="wrapper style1">
<div class="container">
<div id="content">
<!-- Content -->
<article>
<header>
<h2>No Sidebar</h2>
<p>All content, all the time.</p>
</header>
<span class="image featured"><img src="images/banner.jpg" alt="" /></span>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis nisi
consequat etiam lorem ipsum dolor sit amet nullam.</p>
<h3>And Yet Another Subheading</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac quam risus, at tempus
justo. Sed dictum rutrum massa eu volutpat. Quisque vitae hendrerit sem. Pellentesque lorem felis,
ultricies a bibendum id, bibendum sit amet nisl. Mauris et lorem quam. Maecenas rutrum imperdiet
vulputate. Nulla quis nibh ipsum, sed egestas justo. Morbi ut ante mattis orci convallis tempor.
Etiam a lacus a lacus pharetra porttitor quis accumsan odio. Sed vel euismod nisi. Etiam convallis
rhoncus dui quis euismod. Maecenas lorem tellus, congue et condimentum ac, ullamcorper non sapien.
Donec sagittis massa et leo semper a scelerisque metus faucibus. Morbi congue mattis mi.
Phasellus sed nisl vitae risus tristique volutpat. Cras rutrum commodo luctus.</p>
<p>Phasellus odio risus, faucibus et viverra vitae, eleifend ac purus. Praesent mattis, enim
quis hendrerit porttitor, sapien tortor viverra magna, sit amet rhoncus nisl lacus nec arcu.
Suspendisse laoreet metus ut metus imperdiet interdum aliquam justo tincidunt. Mauris dolor urna,
fringilla vel malesuada ac, dignissim eu mi. Praesent mollis massa ac nulla pretium pretium.
Maecenas tortor mauris, consectetur pellentesque dapibus eget, tincidunt vitae arcu.
Vestibulum purus augue, tincidunt sit amet iaculis id, porta eu purus.</p>
</article>
</div>
</div>
</section>
</th:block>
</html>

View File

@ -16,25 +16,8 @@
<script type="text/javascript" th:src="@{/js/user.js}"></script>
</th:block >
<th:block layout:fragment="content">
<div class="game-controls">
<label for="suitCountSelect">무늬 수:</label>
<select id="suitCountSelect">
<option value="1" selected>1개</option>
<option value="2" >2개</option>
<option value="4">4개</option>
</select>
<label for="cardCountSelect">초기 카드 수:</label>
<select id="cardCountSelect">
<option value="3,2">적음 (숨겨진 카드 3/2장)</option>
<option value="4,3" selected>보통 (숨겨진 카드 4/3장)</option>
<option value="5,6">많음 (숨겨진 카드 5/6장)</option>
</select>
<button id="startButton">새 게임 시작</button>
</div>
<div id="game-container">
<canvas id="gameCanvas"></canvas>
</div>
</th:block>
</html>

View File

@ -1,94 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
</script>
</th:block>
<th:block layout:fragment="content" id="content">-->
<!-- Main -->
<section class="wrapper style1">
<div class="container">
<div class="row gtr-200">
<div class="col-8 col-12-narrower">
<div id="content">
<!-- Content -->
<article>
<header>
<h2>Right Sidebar</h2>
<p>Sidebar on the right, content on the left.</p>
</header>
<span class="image featured"><img src="images/banner.jpg" alt="" /></span>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis nisi
consequat etiam lorem ipsum dolor sit amet nullam.</p>
<h3>And Yet Another Subheading</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac quam risus, at tempus
justo. Sed dictum rutrum massa eu volutpat. Quisque vitae hendrerit sem. Pellentesque lorem felis,
ultricies a bibendum id, bibendum sit amet nisl. Mauris et lorem quam. Maecenas rutrum imperdiet
vulputate. Nulla quis nibh ipsum, sed egestas justo. Morbi ut ante mattis orci convallis tempor.
Etiam a lacus a lacus pharetra porttitor quis accumsan odio. Sed vel euismod nisi. Etiam convallis
rhoncus dui quis euismod. Maecenas lorem tellus, congue et condimentum ac, ullamcorper non sapien.
Donec sagittis massa et leo semper a scelerisque metus faucibus. Morbi congue mattis mi.
Phasellus sed nisl vitae risus tristique volutpat. Cras rutrum commodo luctus.</p>
<p>Phasellus odio risus, faucibus et viverra vitae, eleifend ac purus. Praesent mattis, enim
quis hendrerit porttitor, sapien tortor viverra magna, sit amet rhoncus nisl lacus nec arcu.
Suspendisse laoreet metus ut metus imperdiet interdum aliquam justo tincidunt. Mauris dolor urna,
fringilla vel malesuada ac, dignissim eu mi. Praesent mollis massa ac nulla pretium pretium.
Maecenas tortor mauris, consectetur pellentesque dapibus eget, tincidunt vitae arcu.
Vestibulum purus augue, tincidunt sit amet iaculis id, porta eu purus.</p>
</article>
</div>
</div>
<div class="col-4 col-12-narrower">
<div id="sidebar">
<!-- Sidebar -->
<section>
<h3>Just a Subheading</h3>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>
<footer>
<a href="#" class="button">Continue Reading</a>
</footer>
</section>
<section>
<h3>Another Subheading</h3>
<ul class="links">
<li><a href="#">Amet turpis, feugiat et sit amet</a></li>
<li><a href="#">Ornare in hendrerit in lectus</a></li>
<li><a href="#">Semper mod quis eget mi dolore</a></li>
<li><a href="#">Consequat etiam lorem phasellus</a></li>
<li><a href="#">Amet turpis, feugiat et sit amet</a></li>
<li><a href="#">Semper mod quisturpis nisi</a></li>
</ul>
<footer>
<a href="#" class="button">More Random Links</a>
</footer>
</section>
</div>
</div>
</div>
</div>
</section>
</th:block>
</html>

View File

@ -1,123 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/default_layout}">
<th:block layout:fragment="head">
<script type="text/javascript" th:src="@{/js/blog.js}"></script>
<link th:href="@{/css/blog.css}" rel="stylesheet" />
<script th:inline="javascript">
</script>
</th:block>
<th:block layout:fragment="content" id="content">
<!-- Main -->
<section class="wrapper style1">
<div class="container">
<div class="row gtr-200">
<div class="col-3 col-12-narrower">
<div id="sidebar1">
<!-- Sidebar 1 -->
<section>
<h3>Just a Subheading</h3>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>
<footer>
<a href="#" class="button">Continue Reading</a>
</footer>
</section>
<section>
<h3>Another Subheading</h3>
<ul class="links">
<li><a href="#">Amet turpis, feugiat sit amet</a></li>
<li><a href="#">Ornare in hendrerit lectus</a></li>
<li><a href="#">Semper mod quis eget dolore</a></li>
<li><a href="#">Consequat lorem phasellus</a></li>
<li><a href="#">Amet turpis feugiat amet</a></li>
<li><a href="#">Semper mod quisturpis nisi</a></li>
</ul>
<footer>
<a href="#" class="button">More Random Links</a>
</footer>
</section>
</div>
</div>
<div class="col-6 col-12-narrower imp-narrower">
<div id="content">
<!-- Content -->
<article>
<header>
<h2>Two Sidebar</h2>
<p>Yup. Two sidebars at the same time.</p>
</header>
<span class="image featured"><img src="images/banner.jpg" alt="" /></span>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis nisi
consequat etiam lorem ipsum dolor sit amet nullam.</p>
<h3>And Yet Another Subheading</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac quam risus, at tempus
justo. Sed dictum rutrum massa eu volutpat. Quisque vitae hendrerit sem. Pellentesque lorem felis,
ultricies a bibendum id, bibendum sit amet nisl. Mauris et lorem quam. Maecenas rutrum imperdiet
rhoncus dui quis euismod. Maecenas lorem tellus, congue et condimentum ac, ullamcorper non sapien.
Donec sagittis massa et leo semper a scelerisque metus faucibus. Morbi congue mattis mi.
Phasellus sed nisl vitae risus tristique volutpat. Cras rutrum commodo luctus.</p>
<p>Phasellus odio risus, faucibus et viverra vitae, eleifend ac purus. Praesent mattis, enim
quis hendrerit porttitor, sapien tortor viverra magna, sit amet rhoncus nisl lacus nec arcu.
Maecenas tortor mauris, consectetur pellentesque dapibus eget, tincidunt vitae arcu.
Vestibulum purus augue, tincidunt sit amet iaculis id, porta eu purus.</p>
</article>
</div>
</div>
<div class="col-3 col-12-narrower">
<div id="sidebar2">
<!-- Sidebar 2 -->
<section>
<h3>Another Subheading</h3>
<ul class="links">
<li><a href="#">Amet turpis, feugiat sit amet</a></li>
<li><a href="#">Ornare in hendrerit lectus</a></li>
<li><a href="#">Semper mod quis eget dolore</a></li>
<li><a href="#">Consequat lorem phasellus</a></li>
<li><a href="#">Amet turpis feugiat amet</a></li>
<li><a href="#">Semper mod quisturpis nisi</a></li>
</ul>
<footer>
<a href="#" class="button">More Random Links</a>
</footer>
</section>
<section>
<h3>Just a Subheading</h3>
<p>Phasellus quam turpis, feugiat sit amet ornare in, hendrerit in lectus.
Praesent semper mod quis eget mi. Etiam eu ante risus. Aliquam erat volutpat.
Aliquam luctus et mattis lectus sit amet pulvinar. Nam turpis et nisi etiam.</p>
<footer>
<a href="#" class="button">Continue Reading</a>
</footer>
</section>
</div>
</div>
</div>
</div>
</section>
</th:block>
</html>

View File

@ -47,7 +47,9 @@
</ul>
</li>
<th:block sec:authorize="!isAuthenticated()">
<li id="menu_login" ><a href="javascript:openLoginPopup('login')">LOGIN</a></li>
<li id="menu_login">
<a class="open-login-popup" to="#loginPopup">LOGIN</a>
</li>
</th:block>
<th:block sec:authorize="isAuthenticated()">
<li>

View File

@ -10,9 +10,11 @@
<title>BUM'sPace</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="assets/css/main.css" />
<script async th:src="@{https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9504446465764716}" crossorigin="anonymous"></script>
<script type="text/javascript" th:src="@{/js/common.js}"></script>
<link th:href="@{/css/common.css}" rel="stylesheet" />
<link th:href="@{/css/main.css}" rel="stylesheet" />
<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_parameter" th:content="${_csrf.parameterName}"/>
<script th:inline="javascript">

View File

@ -2,7 +2,7 @@
<html lagn="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns="http://www.w3.org/1999/html">
<head>
<base th:href="@{/}" />
@ -12,76 +12,56 @@
</head>
<body class="is-preload">
<div id="page-wrapper">
<th:block th:replace="~{fragments/header :: header}"></th:block>
<th:block th:replace="~{fragments/header :: header}"></th:block>
<th:block layout:fragment="content"></th:block>
<div class="dim_layer">
<div class="dimBg"></div>
<th:block layout:fragment="popup_layer"></th:block>
<!-- 로그인 팝업 -->
<div id="loginPopup" class="pop_layer">
<div class="pop_container">
<div class="pop_conts">
<h2>로그인</h2>
<form id="loginFormElement">
<input type="text" th:data="${enc}" id="loginId" placeholder="아이디" required/>
<input type="password" th:data="${type}" id="loginPassword" placeholder="비밀번호" required/>
<input type="checkbox" id="rememberMe" class="custom-checkbox"/>
<label for="rememberMe" class="custom-label"></label>
<span>자동로그인</span>
<button type="submit" class="button">로그인</button>
</form>
<div class="btn_r">
<a href="#" class="btn_layerClose" onclick="closePopup()">닫기</a>
</div>
</div>
</div>
</div>
<!-- 회원가입 팝업 -->
<div id="signupPopup" class="pop_layer">
<div class="pop_container">
<div class="pop_conts">
<h2>회원가입</h2>
<input type="text" placeholder="아이디" required>
<input type="password" placeholder="비밀번호" required>
<input type="email" placeholder="이메일" required>
<button onclick="submitForm('signup')">가입하기</button>
<div class="btn_r">
<a href="#" class="btn_layerClose" onclick="closePopup()">닫기</a>
</div>
</div>
</div>
</div>
</div>
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
</div>
<div id="overlay" class="login_overlay">
<style>
.custom-checkbox {
display: none; /* 실제 체크박스 숨김 */
}
.custom-label {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #555;
cursor: pointer;
vertical-align: middle;
position: relative;
}
.custom-checkbox:checked + .custom-label {
background-color: #007bff;
border-color: #007bff;
}
.custom-checkbox:checked + .custom-label::after {
content: '';
position: absolute;
top: 3px;
left: 7px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
</style>
<div id="popup" class="login_popup">
<div id="loginForm" class="login_form">
<h2>로그인</h2>
<form id="loginFormElement" onsubmit="return false;">
<input type="text" th:data="${enc}" id="loginId" placeholder="아이디" required/>
<input type="password" th:data="${type}" id="loginPassword" placeholder="비밀번호" required/>
<input type="checkbox" id="rememberMe" class="custom-checkbox"/>
<label for="rememberMe" class="custom-label"></label>
<span>자동로그인</span>
<button type="submit" class="button">로그인</button>
</form>
</div>
<div id="signupForm" class="login_form">
<h2>회원가입</h2>
<input type="text" placeholder="아이디" required>
<input type="password" placeholder="비밀번호" required>
<input type="email" placeholder="이메일" required>
<button onclick="submitForm('signup')">가입하기</button>
</div>
<span class="login_close" onclick="closePopup()">&times;</span>
</div>
</div>
<!-- Scripts -->
<script th:src="@{/assets/js/jquery.min.js}"></script>
<script th:src="@{/assets/js/jquery.dropotron.min.js}"></script>
<script th:src="@{/assets/js/browser.min.js}"></script>
<script th:src="@{/assets/js/breakpoints.min.js}"></script>
<script th:src="@{/assets/js/util.js}"></script>
<script th:src="@{/assets/js/main.js}"></script>
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/jquery.dropotron.min.js}"></script>
<script th:src="@{/js/browser.min.js}"></script>
<script th:src="@{/js/breakpoints.min.js}"></script>
<script th:src="@{/js/util.js}"></script>
<script th:src="@{/js/main.js}"></script>
</body>
</html>