...
This commit is contained in:
parent
97236619f0
commit
2175abbc80
@ -56,6 +56,8 @@ dependencies {
|
||||
implementation ("org.seleniumhq.selenium:selenium-java:4.10.0")
|
||||
|
||||
implementation ("org.commonmark:commonmark:0.18.0")
|
||||
implementation ("net.coobird:thumbnailator:0.4.14")
|
||||
|
||||
|
||||
implementation ("com.drewnoakes:metadata-extractor:2.19.0")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package kr.lunaticbum.back.lun.configs
|
||||
|
||||
import io.qdrant.client.QdrantClient
|
||||
import org.springframework.ai.ollama.api.OllamaApi
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.CacheControl
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
import java.time.Duration
|
||||
|
||||
|
||||
@Configuration
|
||||
@ -17,12 +17,15 @@ class AppConfig : WebMvcConfigurer {
|
||||
@Value("\${resource.location}")
|
||||
private val resourceLocation: String? = null
|
||||
|
||||
val cacheControl: CacheControl = CacheControl.maxAge(Duration.ofDays(365))
|
||||
|
||||
@Bean
|
||||
fun authInterceptor(): BumsInterceptor {
|
||||
return BumsInterceptor()
|
||||
}
|
||||
override fun addResourceHandlers(registry: org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry) {
|
||||
registry.addResourceHandler(resourceHandler).addResourceLocations(resourceLocation)
|
||||
|
||||
registry.addResourceHandler(resourceHandler).addResourceLocations(resourceLocation).setCacheControl(cacheControl)
|
||||
}
|
||||
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
|
||||
@ -4,6 +4,9 @@ import com.google.gson.Gson
|
||||
import jakarta.servlet.http.Cookie
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.ApiKeyWordKey
|
||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncType11
|
||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncTypeKey
|
||||
import kr.lunaticbum.back.lun.model.UserManager
|
||||
import kr.lunaticbum.back.lun.service.JwtService
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@ -98,6 +101,8 @@ class BumsInterceptor : HandlerInterceptor {
|
||||
(it.getAttribute(WRITE_PERMISSION_KEY) as? Boolean)?.let { permission ->
|
||||
if (permission) {
|
||||
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"OK")
|
||||
modelAndView?.modelMap?.put(EncTypeKey, EncType11)
|
||||
modelAndView?.modelMap?.put(ApiKeyWordKey,"Def")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import kr.lunaticbum.back.lun.model.*
|
||||
import kr.lunaticbum.back.lun.service.JwtService
|
||||
import kr.lunaticbum.back.lun.utils.LogService
|
||||
import kr.lunaticbum.back.lun.utils.getFileExtension
|
||||
import net.coobird.thumbnailator.Thumbnails
|
||||
import org.commonmark.node.Node
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
@ -25,13 +26,11 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.core.io.UrlResource
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import reactor.kotlin.core.publisher.toMono
|
||||
import java.io.*
|
||||
import java.net.URLDecoder
|
||||
import java.text.SimpleDateFormat
|
||||
@ -219,16 +218,54 @@ class BlogController() {
|
||||
val firstImg: Element? = doc.select("img")?.first()
|
||||
val imgSrc: String = firstImg?.attr("src") ?: ""
|
||||
it.image = imgSrc
|
||||
it.thumb = imgSrc.replaceBeforeLast(".", "_thumbnail.")
|
||||
generateThumbnail(imgSrc.split("/").last(), 200)
|
||||
it.html = doc.text()
|
||||
}
|
||||
it.title = if ((it.title?.length ?: 0) >= 1) it.title else ""
|
||||
}
|
||||
}.chunked(2))
|
||||
})
|
||||
}catch (ex: Exception){ex.printStackTrace()}
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
fun generateThumbnail(originalPath: String, targetWidth: Int) {
|
||||
try {
|
||||
val originalFile = File("$uploadPath${File.separator}$originalPath")
|
||||
|
||||
// 썸네일 경로 생성 (예: /upload/uuid.jpg → /upload/uuid_thumbnail.jpg)
|
||||
val thumbnailPath = originalFile.path
|
||||
.replaceBeforeLast(".", "_thumbnail.")
|
||||
|
||||
|
||||
val thumbnailFile = File(thumbnailPath)
|
||||
// 썸네일 이미 존재하면 종료
|
||||
if (thumbnailFile.exists()) {
|
||||
println("썸네일 이미 존재: $thumbnailPath")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 원본 파일 존재 확인
|
||||
if (!originalFile.exists()) {
|
||||
println("원본 파일 없음: $originalPath")
|
||||
return
|
||||
}
|
||||
|
||||
// 썸네일 생성 (가로 기준 비율 유지)
|
||||
Thumbnails.of(originalFile)
|
||||
.width(targetWidth)
|
||||
.keepAspectRatio(true)
|
||||
.toFile(thumbnailPath)
|
||||
|
||||
println("썸네일 생성 완료: $thumbnailPath")
|
||||
} catch (e: IOException) {
|
||||
println("썸네일 생성 실패: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("recent")
|
||||
fun recent() : ResultMV{
|
||||
val vm = ResultMV("content/blog/viewer")
|
||||
@ -271,80 +308,75 @@ class BlogController() {
|
||||
}
|
||||
|
||||
@PostMapping("post/imageUpload")
|
||||
fun postImage(@RequestPart("file") upload: MultipartFile, res: HttpServletResponse, req: HttpServletRequest) : ResponseEntity<FileSaveResult> {
|
||||
fun postImage(@RequestPart("file") upload: MultipartFile, res: HttpServletResponse, req: HttpServletRequest): ResponseEntity<FileSaveResult> {
|
||||
var lResultCode = 0
|
||||
var lResultMsg = "Suscces"
|
||||
var out: OutputStream? = null
|
||||
var printWriter: PrintWriter? = null
|
||||
var targetFile : File? = null
|
||||
logService.log("imgUploadPath ${upload.originalFilename}")
|
||||
res.characterEncoding = "utf-8"
|
||||
res.contentType = "text/html;charset=utf-8"
|
||||
var uuid = UUID.randomUUID()
|
||||
var lResultMsg = "Success"
|
||||
var out: FileOutputStream? = null
|
||||
var targetFile: File? = null
|
||||
|
||||
val uuid = UUID.randomUUID()
|
||||
val extension: String = getFileExtension(upload.originalFilename) ?: ""
|
||||
|
||||
try {
|
||||
|
||||
logService.log("imgUploadPath ${uuid.toString()}")
|
||||
|
||||
val bytes = upload.bytes
|
||||
|
||||
var f = File(uploadPath)
|
||||
// logService.log("imgUploadPath ${f.parentFile.parentFile.parentFile.parentFile.absoluteFile}")
|
||||
// logService.log("imgUploadPath ${f.parentFile.parentFile.parentFile.absoluteFile}")
|
||||
// logService.log("imgUploadPath ${f.parentFile.parentFile.absoluteFile}")
|
||||
// logService.log("imgUploadPath ${f.parentFile.absoluteFile}")
|
||||
logService.log("imgUploadPath ${f.exists()}")
|
||||
logService.log("imgUploadPath ${f.absolutePath}")
|
||||
if (f.exists() == false) f.mkdirs()
|
||||
// 실제 이미지 저장 경로
|
||||
val imgUploadPath = (uploadPath + File.separator + uuid).toString() + "." + extension
|
||||
logService.log("imgUploadPath $imgUploadPath")
|
||||
targetFile = File(imgUploadPath)
|
||||
if(targetFile.parentFile.exists() == false)targetFile.parentFile.mkdirs()
|
||||
val f = File(uploadPath)
|
||||
if (!f.exists()) f.mkdirs()
|
||||
|
||||
// 이미지 저장
|
||||
out = FileOutputStream(imgUploadPath)
|
||||
// 원본 이미지 저장 경로
|
||||
val originalImagePath = "$uploadPath${File.separator}$uuid.$extension"
|
||||
logService.log("Original image path: $originalImagePath")
|
||||
|
||||
// 썸네일 저장 경로
|
||||
val thumbnailPath = "$uploadPath${File.separator}${uuid}_thumbnail.$extension"
|
||||
logService.log("Thumbnail path: $thumbnailPath")
|
||||
|
||||
targetFile = File(originalImagePath)
|
||||
if (!targetFile.parentFile.exists()) targetFile.parentFile.mkdirs()
|
||||
|
||||
// 원본 이미지 저장
|
||||
out = FileOutputStream(originalImagePath)
|
||||
out.write(bytes)
|
||||
out.flush()
|
||||
|
||||
// ckEditor 로 전송
|
||||
// printWriter = res.writer
|
||||
// val callback = req.getParameter("CKEditorFuncNum")
|
||||
// val fileUrl = "/blog/post/image/$uuid.$extension"
|
||||
// 썸네일 생성 및 저장
|
||||
Thumbnails.of(originalImagePath)
|
||||
.width(200) // 가로 크기를 설정
|
||||
.keepAspectRatio(true)
|
||||
.toFile(thumbnailPath)
|
||||
|
||||
// printWriter.println(
|
||||
// ("<script type='text/javascript'>"
|
||||
// + "window.parent.CKEDITOR.tools.callFunction("
|
||||
// + callback + ",'" + fileUrl + "','이미지를 업로드하였습니다.')"
|
||||
// + "</script>")
|
||||
// )
|
||||
//
|
||||
// printWriter.flush()
|
||||
logService.log("imgUploadPath $imgUploadPath")
|
||||
logService.log("imgUploadPath ${File(imgUploadPath).exists()}")
|
||||
val metadata: Metadata? = ImageMetadataReader.readMetadata(File(imgUploadPath))
|
||||
logService.log("Original image saved: ${File(originalImagePath).exists()}")
|
||||
logService.log("Thumbnail saved: ${File(thumbnailPath).exists()}")
|
||||
|
||||
// 메타데이터 읽기 (원본 이미지에서)
|
||||
val metadata: Metadata? = ImageMetadataReader.readMetadata(File(originalImagePath))
|
||||
metadata?.let {
|
||||
it.directories?.forEach { directory ->
|
||||
logService.log(directory.name)
|
||||
logService.log(directory.tags.map { tag -> logService.log("tag.tagName >>> ${tag.tagName} || tag.description ${tag.description}")}.joinToString(" \n"))
|
||||
logService.log(directory.tags.map { tag ->
|
||||
logService.log("tag.tagName >>> ${tag.tagName} || tag.description ${tag.description}")
|
||||
}.joinToString(" \n"))
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
lResultCode = 1
|
||||
lResultMsg = "Error: ${e.message}"
|
||||
} finally {
|
||||
try {
|
||||
out?.close()
|
||||
printWriter?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(FileSaveResult().apply {
|
||||
|
||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(FileSaveResult().apply {
|
||||
this.resultCode = lResultCode
|
||||
this.resultMsg = lResultMsg
|
||||
this.fileName = "$uuid.$extension"
|
||||
this.thumbnailName = "${uuid}_thumbnail.$extension"
|
||||
})
|
||||
return responce
|
||||
}
|
||||
|
||||
}
|
||||
@ -36,6 +36,7 @@ class Post {
|
||||
|
||||
var html : String? = null
|
||||
var image : String? = null
|
||||
var thumb : String? = null
|
||||
|
||||
var writer : String? = null
|
||||
var writeTime : Long = 0
|
||||
|
||||
@ -18,4 +18,5 @@ open class LoginResult : ResponceResult() {
|
||||
@Getter
|
||||
class FileSaveResult : ResponceResult() {
|
||||
var fileName : String? = null
|
||||
var thumbnailName : String? = null
|
||||
}
|
||||
@ -3385,3 +3385,58 @@ button.small,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*Login Popup*/
|
||||
.login_overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.login_popup {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.login_form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login_form h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.login_form input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/*.login_form button {*/
|
||||
/* width: 100%;*/
|
||||
/* padding: 10px;*/
|
||||
/* background-color: #4CAF50;*/
|
||||
/* color: white;*/
|
||||
/* border: none;*/
|
||||
/* cursor: pointer;*/
|
||||
/*}*/
|
||||
|
||||
.login_close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -48,9 +48,9 @@
|
||||
border-radius: 10px;
|
||||
background: #00000044;
|
||||
}
|
||||
#title_field {
|
||||
font-size: 20px;
|
||||
}
|
||||
/*#title_field {*/
|
||||
/*font-size: 20px;*/
|
||||
/*}*/
|
||||
|
||||
.pop_layer .pop_container {
|
||||
padding: 20px 25px;
|
||||
|
||||
@ -16,8 +16,8 @@ let baseData = {
|
||||
'writeTime' : 0,
|
||||
}
|
||||
|
||||
function goToEditor(path,id,sk) {
|
||||
location.href = path + id+"?token="+sk;
|
||||
function goToEditor(id) {
|
||||
location.href = getMainPath() + '/blog/editor/' + id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const loginForm = document.getElementById('loginFormElement');
|
||||
loginForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault(); // 기본 폼 제출 동작 방지
|
||||
submitLoginForm();
|
||||
});
|
||||
});
|
||||
onload = function() {
|
||||
history.replaceState({}, null, location.pathname);
|
||||
// var accToken = get_cookie("access")
|
||||
@ -8,6 +14,31 @@ onload = function() {
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
// onbeforeunload = function () {
|
||||
// var accToken = get_cookie("access")
|
||||
@ -261,3 +292,74 @@ function layer_popup(el){
|
||||
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 submitForm(formType) {
|
||||
// alert(formType === 'login' ? '로그인 시도' : '회원가입 시도');
|
||||
// closePopup();
|
||||
// }
|
||||
|
||||
|
||||
function submitLoginForm() {
|
||||
// const id = document.getElementById('loginId').value;
|
||||
// const password = document.getElementById('loginPassword').value;
|
||||
let user_id = document.getElementById('loginId')
|
||||
let user_pw = document.getElementById('loginPassword')
|
||||
let data = {
|
||||
'user_id': user_id.value,
|
||||
'user_pw': user_pw.value,
|
||||
}
|
||||
postLogin(getMainPath()+"/user/login.ajax",user_pw.data,JSON.stringify(data),user_pw.data, function (data) {
|
||||
closePopup()
|
||||
if (data.isOk) {
|
||||
document.cookie = "access=" + data.token.split(";")[0]+";"
|
||||
window.sessionStorage.setItem("REFRESH",data.refresh.split(";")[0])
|
||||
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}]`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// AJAX 요청
|
||||
// fetch('/login', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify({ id, password }),
|
||||
// })
|
||||
// .then(response => response.json())
|
||||
// .then(data => {
|
||||
// if (data.success) {
|
||||
// alert('로그인 성공!');
|
||||
// // 로그인 성공 후 처리 (예: 페이지 리다이렉트)
|
||||
// } else {
|
||||
// alert('로그인 실패: ' + data.message);
|
||||
// }
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.error('Error:', error);
|
||||
// alert('로그인 중 오류가 발생했습니다.');
|
||||
// });
|
||||
}
|
||||
@ -23,30 +23,19 @@
|
||||
baseData.firstPostLon = [[${srcPost.firstPostLon}]];
|
||||
baseData.writeTime = [[${srcPost.writeTime}]];
|
||||
getLocation()
|
||||
var style = getComputedStyle(document.body)
|
||||
console.log(style.getPropertyValue('--ContentVerticalMargin'))
|
||||
console.log(window.c)
|
||||
console.log(style.getPropertyValue('--FooterHeight'))
|
||||
console.log(style.getPropertyValue('--TopHeight'))
|
||||
document.querySelector("#title_field").value = baseData.title
|
||||
var editorHeght = (
|
||||
document.querySelector('#main_layer').getBoundingClientRect().height -
|
||||
(
|
||||
Number(style.getPropertyValue('--ButtonHegit').replace("px","") * 3)
|
||||
+ Number(style.getPropertyValue('--TopHeight').replace("px",""))
|
||||
)
|
||||
)
|
||||
|
||||
// var style = getComputedStyle(document.body)
|
||||
// console.log(style.getPropertyValue('--ContentVerticalMargin'))
|
||||
// console.log(window.c)
|
||||
// console.log(style.getPropertyValue('--FooterHeight'))
|
||||
// console.log(style.getPropertyValue('--TopHeight'))
|
||||
editor = new toastui.Editor({
|
||||
el: document.querySelector('#editor'),
|
||||
previewStyle: 'tab',
|
||||
height: editorHeght + 'px',
|
||||
height: '900px',
|
||||
width:'95%',
|
||||
theme:'dark',
|
||||
usageStatistics : false,
|
||||
toolbar:null,
|
||||
initialValue:baseData.content,
|
||||
theme:"dark",
|
||||
initialEditType:"wysiwyg",
|
||||
hooks: {
|
||||
addImageBlobHook: (blob, callback) => {
|
||||
@ -84,24 +73,32 @@
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="content" id="content">
|
||||
<div id="main_layer">
|
||||
|
||||
<section class="wrapper style2" >
|
||||
<th:block th:if="${PERMISSION == 'OK'}">
|
||||
<div class="container" >
|
||||
<header class="major">
|
||||
<h3><label for="title_field"><input id="title_field" th:value="${srcPost.title}"></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>
|
||||
</header>
|
||||
</div>
|
||||
</th:block>
|
||||
</section>
|
||||
|
||||
<section class="wrapper style2">
|
||||
<th:block th:if="${PERMISSION != 'OK'}">
|
||||
<h1>권한이 없는 뎁쇼?!</h1>
|
||||
</th:block>
|
||||
<th:block th:if="${PERMISSION == 'OK'}">
|
||||
|
||||
<label for="title_field"></label><input id="title_field" class="write_option">
|
||||
|
||||
<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"/>
|
||||
</div>
|
||||
<h1><button id="save" class="write_option" style="width: 100%; position: relative" onclick="save()">저장하셈</button></h1>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
<th:block layout:fragment="popup_layer">
|
||||
<div id="popLayer1" class="pop_layer">
|
||||
|
||||
@ -14,17 +14,6 @@
|
||||
let onChange = () => {console.log(editor.getMarkdown())}
|
||||
document.addEventListener("DOMContentLoaded", onLoaded);
|
||||
function onLoaded() {
|
||||
// 1. 모든 'current' 클래스를 가진 요소를 선택하고 제거
|
||||
const currentElements = document.querySelectorAll('.current');
|
||||
currentElements.forEach(element => {
|
||||
element.classList.remove('current');
|
||||
});
|
||||
|
||||
// 2. 특정 조건에 맞는 요소에 'current' 클래스 추가 (예: ID가 'targetElement'인 요소)
|
||||
const targetElement = document.getElementById('menu_posts');
|
||||
if (targetElement) {
|
||||
targetElement.classList.add('current');
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@ -65,30 +54,16 @@
|
||||
<div class="col-6 col-12-narrower imp-narrower">
|
||||
<div id="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>
|
||||
<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:data="${post.id}">
|
||||
<a href="#" class="image left"><img th:src="${#strings.length(post.image) > 0} ? ${post.image} : 'images/pic01.jpg'" alt="" /></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>
|
||||
<p th:text="${#strings.abbreviate(post.html, 80)}" class="ellipsis"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
@ -30,12 +30,22 @@
|
||||
initialValue:baseData.content,
|
||||
});
|
||||
}
|
||||
function loadEditor() {
|
||||
goToEditor([[${srcPost.id}]]);
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="content" id="content">
|
||||
<section class="wrapper style2">
|
||||
<div class="container">
|
||||
<header class="major" >
|
||||
|
||||
<div class="container" th:if="${PERMISSION == 'OK'}" onclick="loadEditor()">
|
||||
<header class="major">
|
||||
<h2 id="title_layer" th:text="${srcPost.title}">A gigantic heading you can use for whatever</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" th:if="${PERMISSION != 'OK'}" onclick="openLoginPopup('login')">
|
||||
<header class="major">
|
||||
<h2 id="title_layer" th:text="${srcPost.title}">A gigantic heading you can use for whatever</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>
|
||||
|
||||
@ -73,7 +73,12 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="col-4 col-12-narrower">
|
||||
<div class="box highlight">
|
||||
<div class="box highlight" th:if="${PERMISSION == 'OK'}" 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" th:if="${PERMISSION != 'OK'}" onclick="openLoginPopup('login')">
|
||||
<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>
|
||||
|
||||
@ -14,10 +14,8 @@
|
||||
let onChange = () => {console.log(editor.getMarkdown())}
|
||||
document.addEventListener("DOMContentLoaded", onLoaded);
|
||||
function onLoaded() {
|
||||
var h = document.querySelector('#main_layer').getBoundingClientRect().height + 'px'
|
||||
editor = new toastui.Editor({
|
||||
el: document.querySelector('#editor'),
|
||||
height: '500px',
|
||||
width:'100%',
|
||||
viewer: true,
|
||||
usageStatistics : false,
|
||||
@ -137,15 +135,15 @@
|
||||
"> - **Embedded license files**: [jcl-over-slf4j-2.0.16.jar/META-INF/LICENSE.txt](jcl-over-slf4j-2.0.16.jar/META-INF/LICENSE.txt)\n" +
|
||||
"\n" +
|
||||
"\n"
|
||||
,
|
||||
theme:"dark",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="content" id="content">
|
||||
<div id="main_layer" style="background:#30303594;">
|
||||
<section class="wrapper style1">
|
||||
<div class="container">
|
||||
<div id="editor" ></div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</html>
|
||||
|
||||
@ -9,10 +9,14 @@
|
||||
<!-- Nav -->
|
||||
<nav id="nav">
|
||||
<ul>
|
||||
<li id="menu_home" class="current"><a th:href="@{/}">Home</a></li>
|
||||
<li id="menu_home" ><a th:href="@{/}">Home</a></li>
|
||||
<li id="menu_posts"><a href="blog/posts">Posts</a></li>
|
||||
|
||||
<li id="menu_sec"><a href="left-sidebar">Left Sidebar</a></li>
|
||||
<li id="menu_thr"><a href="right-sidebar">Right Sidebar</a></li>
|
||||
<li id="menu_four"><a href="two-sidebar">Two Sidebar</a></li>
|
||||
<li id="menu_drop">
|
||||
<a href="#">Dropdown</a>
|
||||
<a href="#">About</a>
|
||||
<ul>
|
||||
<li><a href="#">Lorem dolor</a></li>
|
||||
<li><a href="#">Magna phasellus</a></li>
|
||||
@ -27,13 +31,9 @@
|
||||
<li><a href="#">Veroeros feugiat</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Veroeros feugiat</a></li>
|
||||
<li><a th:href="@{/licenses}">Licenses</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="menu_sec"><a href="left-sidebar">Left Sidebar</a></li>
|
||||
<li id="menu_thr"><a href="right-sidebar">Right Sidebar</a></li>
|
||||
<li id="menu_four"><a href="two-sidebar">Two Sidebar</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
@ -19,6 +19,26 @@
|
||||
</div>
|
||||
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
|
||||
</div>
|
||||
<div id="overlay" class="login_overlay">
|
||||
<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>
|
||||
<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()">×</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Scripts -->
|
||||
<script th:src="@{/assets/js/jquery.min.js}"></script>
|
||||
<script th:src="@{/assets/js/jquery.dropotron.min.js}"></script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user