From 3a0a29ec02588d4dccb78d8e4ccc01fd23b38fcd Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Mon, 11 Aug 2025 15:55:11 +0900 Subject: [PATCH] ... --- .../back/lun/configs/BumsInterceptor.kt | 6 +- .../back/lun/configs/SecurityConfig.kt | 67 +++--- .../back/lun/controllers/BlogController.kt | 8 +- .../lunaticbum/back/lun/controllers/Home.kt | 66 +++++- .../back/lun/controllers/Telegram.kt | 6 +- .../back/lun/controllers/UserController.kt | 36 ++-- .../kr/lunaticbum/back/lun/model/Post.kt | 23 +- .../back/lun/model/ResponceResult.kt | 3 +- .../kr/lunaticbum/back/lun/model/TokenData.kt | 70 ++++++- .../kr/lunaticbum/back/lun/model/User.kt | 2 +- src/main/resources/static/assets/css/main.css | 3 +- src/main/resources/static/js/blog.js | 196 ++++++++++++------ src/main/resources/static/js/common.js | 6 +- .../templates/content/blog/editor.html | 6 +- .../templates/fragments/includes.html | 1 + 15 files changed, 339 insertions(+), 160 deletions(-) diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/configs/BumsInterceptor.kt b/src/main/kotlin/kr/lunaticbum/back/lun/configs/BumsInterceptor.kt index bd66820..561efcd 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/configs/BumsInterceptor.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/configs/BumsInterceptor.kt @@ -10,6 +10,7 @@ import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncTypeKey import kr.lunaticbum.back.lun.model.UserManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.lang.Nullable +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.web.authentication.RememberMeServices import org.springframework.stereotype.Component import org.springframework.stereotype.Service @@ -47,12 +48,11 @@ class BumsInterceptor : HandlerInterceptor { handler: Any, @Nullable modelAndView: ModelAndView? ) { -// if(remeberMe && authResult != null) { -// rememberMeServices.loginSuccess(httpServletRequest, responce, authResult) -// } + modelAndView?.modelMap?.put(EncTypeKey, EncType11) modelAndView?.modelMap?.put(ApiKeyWordKey,"Def") + if (modelAndView != null) { modelAndView.modelMap.put(EncTypeKey, EncType11) modelAndView.modelMap.put(ApiKeyWordKey, "Def") diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/configs/SecurityConfig.kt b/src/main/kotlin/kr/lunaticbum/back/lun/configs/SecurityConfig.kt index ad23349..fdc8b66 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/configs/SecurityConfig.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/configs/SecurityConfig.kt @@ -3,6 +3,7 @@ package kr.lunaticbum.back.lun.configs import com.fasterxml.jackson.databind.ObjectMapper import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import kr.lunaticbum.back.lun.model.MongoPersistentTokenRepository import kr.lunaticbum.back.lun.model.UserManager import kr.lunaticbum.back.lun.utils.LogService import org.springframework.beans.factory.annotation.Autowired @@ -23,14 +24,20 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.access.AccessDeniedHandler +import org.springframework.security.web.authentication.RememberMeServices +import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl +import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository import org.springframework.web.ErrorResponse +import javax.sql.DataSource @Configuration @EnableWebSecurity class SecurityConfig( private val userManager: UserManager, - private val bCryptPasswordEncoder: BCryptPasswordEncoder + private val bCryptPasswordEncoder: BCryptPasswordEncoder, + private val tokenRepository: MongoPersistentTokenRepository ) { @Autowired lateinit var logService: LogService @@ -42,6 +49,22 @@ class SecurityConfig( } } + // RememberMeServices를 Bean으로 생성하고 필드에 할당하거나, 생성자 주입을 할 수 있음 + + + @Bean + fun rememberMeServices(): RememberMeServices { + val key = "your-remember-me-key" + return PersistentTokenBasedRememberMeServices(key, userManager, + tokenRepository as PersistentTokenRepository? + ).apply { + setParameter("remember-me") // 기본 파라미터명 + setTokenValiditySeconds(86400) // 토큰 유효시간 설정 + // 필요시 setAlwaysRemember(true) 등 추가 설정 가능 + println("CALLED rememberMeServices") + } + } + @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { csrf -> @@ -68,8 +91,9 @@ class SecurityConfig( .defaultSuccessUrl("/", true) .permitAll() }.rememberMe { rememberMe -> - rememberMe - .key("BsTs*!12@") // 보통 안전한 키 지정 + rememberMe.rememberMeServices(rememberMeServices()) + .key("remember-BsTs*!12@") // 보통 안전한 키 지정 + .tokenRepository(tokenRepository) .tokenValiditySeconds(60 * 60 * 24 * 7) // 7일간 유효 .userDetailsService(userManager) // 사용자 정보 서비스 지정 }.logout { logout -> @@ -86,43 +110,6 @@ class SecurityConfig( .passwordEncoder(bCryptPasswordEncoder) return authenticationManagerBuilder.build() // .and() 없이 직접 build() 호출 } -// @Bean -// fun filterChain(http: HttpSecurity): SecurityFilterChain { -// -// http.csrf { -// it.ignoringRequestMatchers("/user/joinUser.bjx").disable() -// } -// http.cors { it.disable() } -// http.headers { -// it.frameOptions { frameOptionsConfig -> -// frameOptionsConfig.disable() -// } -// } -// -// http.authorizeHttpRequests { -// logService.log(it.toString()) -// it.requestMatchers(HttpMethod.POST,"/user/**").permitAll() -// it.requestMatchers("/blog/viewer/**").permitAll() -// it.anyRequest().permitAll() -// } -// http.sessionManagement { -// it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) -// } -// .exceptionHandling { it -> -// it.authenticationEntryPoint(unauthorizedEntryPoint) -// .accessDeniedHandler(accessDeniedHandler) -// } -//// .formLogin { formLogin -> -//// formLogin -//// .loginPage("/user/join") -//////// .usernameParameter("username") -//////// .passwordParameter("password") -//// .loginProcessingUrl("/user/joinUser.api") -//// .defaultSuccessUrl("/", true) -//// } -// -// return http.build() -// } private val unauthorizedEntryPoint = AuthenticationEntryPoint { request: HttpServletRequest?, response: HttpServletResponse, authException: AuthenticationException? -> diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/BlogController.kt b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/BlogController.kt index 906b2d0..90e0f12 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/BlogController.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/BlogController.kt @@ -176,7 +176,13 @@ class BlogController() { val vm = ResultMV("content/blog/viewer") postManager.getPost(postId).block().apply { this?.title = URLDecoder.decode(this?.title) - this?.content = URLDecoder.decode(this?.content) + println("this?.content >>> ${this?.content}") + if (this?.content is String){ + this?.content = URLDecoder.decode(this?.content) + } else { + this?.content = Gson().toJson(this?.content) + } + diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Home.kt b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Home.kt index 746dbbc..6f5238e 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Home.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Home.kt @@ -1,7 +1,11 @@ package kr.lunaticbum.back.lun.controllers +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonParser import jakarta.servlet.http.HttpServletResponse +import kotlinx.coroutines.reactor.awaitSingle import kr.lunaticbum.back.lun.model.PostManager import kr.lunaticbum.back.lun.model.ResultMV import kr.lunaticbum.back.lun.utils.LogService @@ -31,24 +35,68 @@ class Home { @Autowired private lateinit var postManager: PostManager + data class PostView( + val id: Long, + val title: String, + val thumb: String?, + val writeTime: Long, + val textOnly: String, + val firstImage: String? + ) + + data class DeltaOp(val insert: Any) + data class Delta(val ops: List) + + fun extractFromDelta(deltaJson: String): Pair { + + val delta: Delta = Gson().fromJson(deltaJson, Delta::class.java) + + var textOnly = StringBuilder() + var firstImage: String? = null + + delta.ops.forEach { op -> + if (op.insert is String) { + textOnly.append(op.insert) + } else if (op.insert is Map<*, *>) { + val obj = op.insert as Map<*, *> + if (obj["image"] != null && firstImage == null) { + firstImage = obj["image"].toString() + } + } + } + return textOnly.toString() to firstImage + } + @GetMapping("/","/home.bs") - fun home() : ResultMV { + suspend fun home() : ResultMV { val vm = ResultMV("content/home") try { - vm.modelMap.put("Posts", postManager.find4().apply { + vm.modelMap.put("Posts", postManager.find8().apply { this.forEach { it.title = URLDecoder.decode(it.title) it.content = URLDecoder.decode(it.content) val parser: Parser = Parser.builder().build() val document: Node = parser.parse(it.content) val renderer = HtmlRenderer.builder().build() - Jsoup.parse(renderer.render(document))?.let { doc -> - val firstImg: Element? = doc.select("img")?.first() - val imgSrc: String = firstImg?.attr("src") ?: "" - it.image = imgSrc - it.thumb = imgSrc.replace(imgSrc.split("/").last(), imgSrc.split("/").last().replace(".","_thumbnail.")) - generateThumbnail(imgSrc.split("/").last(), 200) - it.html = doc.text() + println("content >>> ${it.content}") + try { + JsonParser.parseString(it.content) + it.content?.let { content -> + var delta = extractFromDelta(content) + val firstImg = delta.second + it.image = firstImg ?: "images/pic01.jpg" + it.thumb = firstImg ?: "images/pic01.jpg" + it.html = delta.first + } + } catch (e: Exception) { + Jsoup.parse(renderer.render(document))?.let { doc -> + val firstImg: Element? = doc.select("img")?.first() + val imgSrc: String = firstImg?.attr("src") ?: "" + it.image = imgSrc + it.thumb = imgSrc.replace(imgSrc.split("/").last(), imgSrc.split("/").last().replace(".","_thumbnail.")) + generateThumbnail(imgSrc.split("/").last(), 200) + it.html = doc.text() + } } it.title = if ((it.title?.length ?: 0) >= 1) it.title else "" } diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt index 89898c1..a93f5e6 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt @@ -77,7 +77,11 @@ class Telegram { jsonString.extractModelData { exception, originDataString -> if (exception == null) { Gson().fromJson(originDataString, ReportModel::class.java)?.let { msg -> - sendMsg("${msg.name}님이 전송\n${msg.message}\n회신가능 메일${msg.email}") + WebClient.create().get() + .uri("https://api.telegram.org/${globalEvv.telegramBotKey}/sendMessage?chat_id=${globalEvv.telegramMyId}&text=${msg.name}님이 전송\n${msg.message}\n회신가능 메일${msg.email}") + .retrieve() + .bodyToMono(String::class.java).block() ?: "FAIL" + } } } diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/UserController.kt b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/UserController.kt index 0cc8740..c3a11a2 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/UserController.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/UserController.kt @@ -32,7 +32,9 @@ import javax.naming.AuthenticationException @RestController @RequestMapping("/user") -class UserController { +class UserController( + private val rememberMeServices: RememberMeServices +) { @Autowired @@ -84,17 +86,17 @@ class UserController { @ResponseBody @PostMapping("login.bjx") - fun login(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity { + fun login(httpServletRequest: HttpServletRequest, + httpRes: HttpServletResponse, + @RequestBody jsonString: String) : ResponseEntity { + var loginResult = LoginResult().apply { + this.isOk = false + this.resultCode = 9999 + this.resultMsg = "JUST INITIALIZED" + } try { - - logService.log(httpServletRequest.requestURI) - logService.log(jsonString) var lResultCode = 0 var lResultMsg = "Suscces" - var u : UserDetails? = null - var user : User? = null - var tokenData : TokenData? = null - var remeberMe = false var authResult : Authentication? = null jsonString.extractModelData { exception, originDataString -> if (exception == null) { @@ -107,6 +109,7 @@ class UserController { // 인증 성공 시 SecurityContextHolder에 인증 정보 저장 SecurityContextHolder.getContext().authentication = authResult + // 인증 정보가 담긴 SecurityContext를 세션에 저장 val session = httpServletRequest.getSession(true) session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()) @@ -114,11 +117,11 @@ class UserController { val principal = authResult?.principal if (principal is UserDetails) { - val username = principal.username - - val session = httpServletRequest.getSession(true) - session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()) - remeberMe = target.remeberMe ?: false + println("target.remeberMe >>> ${target.rememberMe}") + loginResult.rememberMe = target.rememberMe + if (target.rememberMe == true) { + rememberMeServices.loginSuccess(httpServletRequest, httpRes, authResult) + } } else { lResultMsg = "is wrong infomation id or passord" lResultCode = 7100 @@ -134,11 +137,10 @@ class UserController { } val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).headers { - }.body(LoginResult().apply { + }.body(loginResult.apply { this.isOk = lResultCode == 0 this.resultCode = lResultCode this.resultMsg = lResultMsg - this.token = if (remeberMe) "OK" else "" }).apply { } @@ -151,8 +153,6 @@ class UserController { this.isOk = false this.resultCode = -999 this.resultMsg = e.message ?: "unknown exception" - this.token = "" - this.refresh = "" }).apply { diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/Post.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/Post.kt index 8c64429..5ca69b5 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/model/Post.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/Post.kt @@ -4,6 +4,7 @@ import kr.lunaticbum.back.lun.utils.LogService import lombok.AllArgsConstructor import lombok.Data import lombok.NoArgsConstructor +import okio.Timeout import org.bson.BsonType import org.bson.codecs.pojo.annotations.BsonId import org.bson.codecs.pojo.annotations.BsonRepresentation @@ -15,6 +16,7 @@ import org.springframework.data.mongodb.core.mapping.Document import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query import org.springframework.data.mongodb.core.query.Update +import org.springframework.data.mongodb.repository.Aggregation import org.springframework.data.mongodb.repository.ReactiveMongoRepository import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Repository @@ -101,6 +103,14 @@ interface PostRepository : ReactiveMongoRepository { fun countByOrderByModifyTimeDesc(): Mono fun findTop5ByOrderByReadCountDesc(): Flux fun findTop5ByOrderByModifyTimeDesc(): Flux + @Aggregation(pipeline = [ + "{ \$sort: { modifyTime: -1 } }", + "{ \$group: { _id: \"\$originId\", post: { \$first: \"\$\$ROOT\" } } }", + "{ \$sort: { \"post.modifyTime\": -1 } }", + "{ \$limit: 8 }", + "{ \$replaceRoot: { newRoot: \"\$post\" } }" + ]) + fun findLatestUniqueOrigin(): Flux } @@ -159,16 +169,9 @@ class PostManager( - fun find4() : List { - val originalList = postRepository.findAllByModifyTime(0) - .takeLast(4) - .buffer(4) - .blockLast(Duration.ofSeconds(30)) ?: listOf() - - return originalList + List((4 - originalList.size).coerceAtLeast(0)) { - Post() // 기본값 생성 (필드 초기화 필요) - } -// return postRepository.findAllByModifyTime(0).takeLast(4).buffer(4).blockLast(Duration.ofSeconds(30)) ?: listOf() + fun find8() : List { + return postRepository.findLatestUniqueOrigin().collectList() // Mono>로 변환 // Flux → Mono> + .block(Duration.ofSeconds(30)) ?: emptyList() } fun find20() : List { diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/ResponceResult.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/ResponceResult.kt index b9a88cf..a88c3cf 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/model/ResponceResult.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/ResponceResult.kt @@ -16,8 +16,7 @@ open class PostsResult : BaseResult() { @Getter open class LoginResult : ResponceResult() { - var token: String? = null - var refresh: String? = null + var rememberMe: Boolean? = null } @Getter diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/TokenData.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/TokenData.kt index ab455cd..a7877cd 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/model/TokenData.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/TokenData.kt @@ -7,7 +7,11 @@ import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.index.Indexed import org.springframework.data.mongodb.core.mapping.Document import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository +import org.springframework.stereotype.Component import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.time.LocalDateTime import java.util.* @@ -38,4 +42,68 @@ class TokenData { interface TokenDataRepository : ReactiveMongoRepository { fun findBytokenKey(tokenKey: String): Mono fun deleteBytokenKey(tokenKey: String) -} \ No newline at end of file +} +interface PersistentTokenRepository { + fun createNewToken(token: PersistentRememberMeToken) + fun updateToken(series: String, tokenValue: String, lastUsed: Date) + fun getTokenForSeries(seriesId: String): PersistentRememberMeToken? + fun removeUserTokens(username: String) +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Document(collection = "persistent_logins") +data class PersistentLogin( + @Id val series: String, + val username: String, + val tokenValue: String, + val lastUsed: Date +) + +interface PersistentLoginRepository : ReactiveMongoRepository { + fun findByUsername(username: String): Flux +} + +@Component +class MongoPersistentTokenRepository ( + private val repository: PersistentLoginRepository +) : PersistentTokenRepository { + + override fun createNewToken(token: PersistentRememberMeToken) { + val persistentLogin = PersistentLogin( + series = token.series, + username = token.username, + tokenValue = token.tokenValue, + lastUsed = token.date + ) + repository.save(persistentLogin).block() // 블로킹 여부는 환경에 따라 조절 + println("CALLED rememberMeServices") + } + + override fun updateToken(series: String, tokenValue: String, lastUsed: Date) { + val login = repository.findById(series).block() + if (login != null) { + val updated = login.copy(tokenValue = tokenValue, lastUsed = lastUsed) + repository.save(updated).block() + println("CALLED rememberMeServices") + } + } + + override fun getTokenForSeries(seriesId: String): PersistentRememberMeToken? { + val login = repository.findById(seriesId).block() + return login?.let { + println("CALLED rememberMeServices") + PersistentRememberMeToken(it.username, it.series, it.tokenValue, it.lastUsed) + + } + } + + override fun removeUserTokens(username: String) { + val tokens = repository.findByUsername(username).collectList().block() + tokens?.let { + println("CALLED rememberMeServices") + repository.deleteAll(it).block() + } + } +} diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/User.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/User.kt index 746bfc2..5831f01 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/model/User.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/User.kt @@ -43,7 +43,7 @@ class User { var isAccept : String? = null var isAdmin : String? = null - var remeberMe : Boolean? = false + var rememberMe : Boolean? = false fun checkValid() : Boolean { if ( diff --git a/src/main/resources/static/assets/css/main.css b/src/main/resources/static/assets/css/main.css index 32eaf81..91a47b3 100644 --- a/src/main/resources/static/assets/css/main.css +++ b/src/main/resources/static/assets/css/main.css @@ -3439,4 +3439,5 @@ button.small, right: 10px; font-size: 20px; cursor: pointer; -} \ No newline at end of file +} + diff --git a/src/main/resources/static/js/blog.js b/src/main/resources/static/js/blog.js index b4c90d9..1899d95 100644 --- a/src/main/resources/static/js/blog.js +++ b/src/main/resources/static/js/blog.js @@ -14,90 +14,150 @@ function initEditor(useEditor) { baseData.firstPostLat = serverData.firstPostLat; baseData.firstPostLon = serverData.firstPostLon; baseData.writeTime = serverData.writeTime; + baseData.originId = serverData.originId; getLocation(); setEditorHeight(); window.addEventListener('resize', setEditorHeight); - 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); -} - -function isDelta(content) { try { - // Delta는 JSON이면서 'ops'라는 키를 포함 - if (typeof content === "string") { - content = JSON.parse(content); + 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 + "
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 객체 형태 } - return typeof content === "object" && content.ops !== undefined; } catch (e) { - return false; // JSON 파싱 실패하면 마크업(HTML)으로 간주 + // JSON 파싱 실패 → HTML로 간주 } + return null; // Delta 아님 +} +function loadEditor() { + location.href = getMainPath() + '/blog/editor/' + serverData.id; } - - function loadContent(content) { - if (isDelta(content)) { - quill.setContents(content); // Delta 데이터라면 setContents + console.log("content >>> ", content); + const delta = parseDelta(content); + if (delta) { + quill.setContents(delta); } else { - quill.clipboard.dangerouslyPasteHTML(content); // HTML 데이터라면 dangerouslyPasteHTML + quill.clipboard.dangerouslyPasteHTML(content); } } + function save() { - onclickWrite(serverData.enc ,serverData.keyword,quill.getContents()) + 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을 입력하거나 빈칸으로 두시면 파일 업로드를 합니다."); @@ -237,8 +297,8 @@ function onclickWrite(type, keyword, html) { if (hasValues) { baseData.title = encodeURIComponent(title_field.value) baseData.content = encodeURIComponent(html) - baseData.firstPostLat = encodeURIComponent(currentLat) - baseData.firstPostLon = encodeURIComponent(currentLon) + baseData.modifyLat = encodeURIComponent(currentLat) + baseData.modifyLon = encodeURIComponent(currentLon) } let uploadUrl = getMainPath() + "/blog/post.bjx"; if(confirm(JSON.stringify(baseData) + "\n해당 내용으로\n유저 등록 하실??")) { diff --git a/src/main/resources/static/js/common.js b/src/main/resources/static/js/common.js index e50ce0f..309a70b 100644 --- a/src/main/resources/static/js/common.js +++ b/src/main/resources/static/js/common.js @@ -419,16 +419,16 @@ 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, + '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.cookie = "access=" + data.token.split(";")[0]+";" - window.sessionStorage.setItem("REFRESH",data.refresh.split(";")[0]) document.location.replace(location.href) } else { if (data.resultCode === 7100) { diff --git a/src/main/resources/templates/content/blog/editor.html b/src/main/resources/templates/content/blog/editor.html index 30f4598..94f1e5f 100644 --- a/src/main/resources/templates/content/blog/editor.html +++ b/src/main/resources/templates/content/blog/editor.html @@ -27,10 +27,11 @@
- +
-

+

@@ -42,6 +43,7 @@

권한이 없는 뎁쇼?!

+
diff --git a/src/main/resources/templates/fragments/includes.html b/src/main/resources/templates/fragments/includes.html index 03cd48f..1add355 100644 --- a/src/main/resources/templates/fragments/includes.html +++ b/src/main/resources/templates/fragments/includes.html @@ -18,6 +18,7 @@