Commit 7f195dc7 authored by shj's avatar shj

[IMPLEMENT] 인덱스 관리 dialog 구현

parent 68d538d8
......@@ -23,6 +23,12 @@ public class GuideTestController {
GuideService guideService;
GuideTestService guideTestService;
@GetMapping
public ResponseEntity<?> getGuide(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideTestService.findGuideById(guideId), HttpStatus.OK);
}
@ApiOperation(value="모든 가이드 목차 조회")
@GetMapping("/index")
public ResponseEntity<?> findIndex()throws Exception{
......@@ -45,8 +51,8 @@ public class GuideTestController {
}
@PutMapping("/index")
public ResponseEntity<?> updateGuideIndex(@RequestBody Guide guide) throws Exception{
return new ResponseEntity<>(guideService.updateGuide(guide), HttpStatus.OK);
public ResponseEntity<?> updateGuideIndex(@RequestBody GuideTest guide) throws Exception{
return new ResponseEntity<>(guideTestService.updateGuide(guide), HttpStatus.OK);
}
......@@ -56,16 +62,10 @@ public class GuideTestController {
}
@GetMapping
public ResponseEntity<?> getGuide(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideTestService.findGuideById(guideId), HttpStatus.OK);
}
@DeleteMapping
public ResponseEntity<?> deleteGuideIndex(@RequestParam(value = "guideId") String guideId, @RequestParam(value = "cascade", required = false) boolean cascade) throws Exception{
return new ResponseEntity<>(guideService.deleteGuide(guideId, cascade), HttpStatus.OK);
public ResponseEntity<?> deleteGuideIndex(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideTestService.deleteGuide(guideId), HttpStatus.OK);
}
@PutMapping
......
package com.vazil.vridge.docs.service;
import com.vazil.vridge.docs.model.Guide;
import com.vazil.vridge.docs.model.GuideTest;
import com.vazil.vridge.docs.repository.GuideTestRepository;
import com.vazil.vridge.docs.utils.TimeManager;
......@@ -100,6 +101,10 @@ public class GuideTestService {
@Transactional
public GuideTest createGuide(GuideTest guide) throws Exception{
// guide.setOrder(guide.getOrder() == null ? getLastOrder(guide.getParentId()) : 0);
// todo : 기존 인덱스와 위치가 중복될 경우, 기존 인덱스들 order + 1
guide.setLike(0);
guide.setView(0);
guide.setCreateDate(TimeManager.now());
......@@ -108,11 +113,24 @@ public class GuideTestService {
@Transactional
public GuideTest updateGuide(GuideTest guide) throws Exception{
GuideTest savedGuide = repository.findById(guide.getId()).orElseThrow(NoSuchElementException::new);
GuideTest db = repository.findById(guide.getId()).orElseThrow(NoSuchElementException::new);
// todo : 기존 인덱스와 위치가 중복될 경우, 기존 인덱스들 order + 1
savedGuide.setOrder(guide.getOrder());
savedGuide.setUpdateDate(TimeManager.now());
return repository.save(savedGuide);
db.setLocale(guide.getLocale());
db.setTitle(guide.getTitle());
db.setOrder(guide.getOrder());
db.setPath(guide.getPath());
db.setDepth(guide.getDepth());
return repository.save(db);
}
@Transactional
public String deleteGuide(String guideId) throws Exception{
GuideTest db = repository.findById(guideId).orElseThrow(NoSuchElementException::new);
repository.delete(db);
return db.getId();
}
}
......@@ -12,7 +12,7 @@
padding-right: 240px;
}
.guide-wrap {
min-height: 100vh !important;
min-height: 90vh !important;
max-width: 1280px !important;
margin:0 auto;
margin-top:36px;
......@@ -46,7 +46,7 @@
}
.guide-content{
margin-top:200px;
margin-top:130px;
width:100% !important;
h2{
......
......@@ -8,10 +8,10 @@
>
<v-list-item
v-if="item.children.length === 0"
@click="clickChildEvent(item)"
@click="clickItemEvent(item)"
:ripple="false"
:class="isActiveMenu(item.id) ? 'active' : ''"
:style="isActiveMenu(item.id) ? 'border-left: 2px solid #1E88E5;' : 'border-left: 1px solid rgba(0,0,0,0.15);'"
:style="{'border-left': depth !== 1 && '1px solid rgba(0,0,0,0.15)'}"
:class="selectedItem === item ? 'active' : ''"
>
<v-list-item-title v-text="item.title" />
</v-list-item>
......@@ -20,9 +20,9 @@
v-else
v-model="item.active"
:ripple="false"
:class="isActiveMenu(item.id) ? 'active' : ''"
:sub-group="depth > 1 ? true : false"
:style="isActiveMenu(item.id) ? 'border-left: 2px solid #1E88E5;' : 'border-left: 1px solid rgba(0,0,0,0.15);'"
:class="selectedItem === item ? 'active' : ''"
:style="{'border-left': depth !== 1 && '1px solid rgba(0,0,0,0.15)'}"
prepend-icon=""
>
<template v-slot:appendIcon>
......@@ -32,7 +32,7 @@
<template v-slot:activator>
<v-list-item-content
v-if="item"
@click="clickParentEvent(item)"
@click="clickItemEvent(item)"
active-class="active"
>
<v-list-item-title v-text="item.title" />
......@@ -42,8 +42,8 @@
<tree
:items="item.children"
:depth="depth+1"
@clickParentEvent="clickParentEvent"
@clickChildEvent="clickChildEvent"
:selectedItem="selectedItem"
@clickItemEvent="clickItemEvent"
/>
</v-list-group>
</div>
......@@ -58,6 +58,9 @@ export default {
type: Array,
required: true,
},
selectedItem: {
type: Object,
},
depth: {
type: [String, Number],
default: 1
......@@ -67,15 +70,11 @@ export default {
active: [],
}),
methods: {
clickParentEvent(item){
this.$emit('clickParentEvent', item)
},
clickChildEvent(item){
this.$emit('clickChildEvent', item)
clickItemEvent(item){
this.$emit('clickItemEvent', item)
},
isActiveMenu(id) {
return this.$route.path.indexOf(id) > -1
},
watch: {
},
mounted() {
},
......
......@@ -65,8 +65,8 @@
<v-list v-if="guideIndex && !loadingPageList">
<tree
:items="guideIndex"
@clickParentEvent="openGuide"
@clickChildEvent="openGuide"
:selectedItem="selectedIndex"
@clickItemEvent="openGuide"
/>
</v-list>
......@@ -98,11 +98,28 @@
<img @click="$router.push('/')" src="/logo_v2/01.png" cover height="28"/>
</span>
<v-btn
v-if="$store.state.editMode"
@click="editDialogActive = true"
elevation="0"
color="primary"
class="ml-4"
>인덱스 관리</v-btn>
<v-btn
v-if="$store.state.editMode"
@click="$store.state.editMode = false"
elevation="0"
color="primary"
class="ml-4"
>편집모드 해제</v-btn>
<v-spacer/>
<div class="search-container" v-show="showSearch">
<v-text-field
v-model="keyword"
@keydown.enter="editOn"
:maxlength="50"
solo
dense
......@@ -138,50 +155,108 @@
</v-row>
<v-progress-linear absolute bottom :value="scrollPositionRate" height="1" background-color="#eee" color="primary" style="transition:none !important;"></v-progress-linear>
</v-app-bar>
<v-main>
<client-only>
<Nuxt/>
<v-dialog
v-model="editDialogActive"
>
<div class="edit-dialog-container">
<h2 class="pa-4 text-center">인덱스 관리</h2>
<!-- <vridge-dialog
ref="newPageDialog"
<v-icon @click="formData = null, editFormMethod = '', editDialogActive = false" class="close-btn">mdi-close</v-icon>
<v-row class="pa-10 pt-0 justify-space-around" no-gutters style="width: 100%; height: calc(100% - 68px);">
<v-col cols="5" style="height: 100%;">
<v-subheader>인덱스 목록</v-subheader>
<v-list v-if="editModeList && !loadingPageList">
<tree
:items="editModeList"
:selectedItem="formData"
@clickItemEvent="setEditTarget"
/>
</v-list>
</v-col>
<v-col cols="5" style="height: 100%;">
<div style="height: 35%;">
<v-subheader>인덱스 이상치</v-subheader>
<v-list>
<v-list-item
v-for="(item, index) in editModeRemainList"
:key="index"
@click="setEditTarget(item)"
>
<template
v-slot:content
>
<v-text-field v-model="createGuideTitle" label="타이틀" :maxlength="50"></v-text-field>
<v-list-item-title>{{item.title}}</v-list-item-title>
</v-list-item>
</v-list>
</div>
<v-tooltip
bottom>
<template v-slot:activator="{on,attrs}">
<!-- form -->
<div v-if="formData" style="height: 60%;">
<v-subheader>
{{editFormMethod}}
<v-spacer></v-spacer>
<v-icon size="16" @click="formData = null, editFormMethod = ''">mdi-close</v-icon>
</v-subheader>
<v-form>
<v-container class="pt-6 text-center">
<v-select
:items="['ko','en']"
v-model="formData.locale"
label="Locale"
dense
/>
<v-text-field
v-model="formData.title"
label="Title"
type="text"
/>
<v-text-field
v-model="formData.order"
label="Order"
type="number"
/>
<v-text-field
v-model="formData.path"
label="Path (ex: 'propect/dashboard/')"
@input="setDepthValue"
type="text"
/>
<v-text-field
v-model="formData.depth"
:disabled="editFormMethod !== '수정'"
label="Depth"
type="number"
/>
<v-btn
v-bind="attrs"
v-on="on"
@click="editFormMethod === '추가' ? createIndex() : updateIndex()"
elevation="0"
icon
>
<v-icon small>mdi-comment-question</v-icon>
</v-btn>
</template>
<span>
예시)
https://api.github.com/repos/vazilcompany/vridge-docs/contents/guide/ko/overview/overview.md 일경우
>> overview/overview.md 입력
</span>
</v-tooltip>
<v-text-field v-model="createGuideContentKey" label="가이드 github 경로"></v-text-field>
color="primary"
>저장</v-btn>
<v-btn
@click="removeIndex"
elevation="0"
color="primary"
:disabled="editFormMethod !== '수정'"
>삭제</v-btn>
</v-container>
</v-form>
</div>
</template>
<template v-slot:actionButton>
<v-btn
v-else
@click="editFormMethod = '추가', formData={locale: 'ko', order: 0, depth: 1}"
elevation="0"
color="primary"
>인덱스 추가</v-btn>
</v-col>
</v-row>
</div>
</v-dialog>
</v-app-bar>
<v-btn @click="createGuide()">등록</v-btn>
</template>
</vridge-dialog> -->
<v-main>
<client-only>
<Nuxt/>
</client-only>
</v-main>
......@@ -207,6 +282,7 @@ export default{
clipped: false,
rawIndexList:[],
guideIndex:[],
selectedIndex:null,
loadingPageList: false,
targetParentId: null,
createGuideTitle : '',
......@@ -215,6 +291,13 @@ export default{
call:0,
scrollPositionRate:0,
showSearch:false,
// 인덱스 관리
editDialogActive: false,
editModeList: [],
editModeRemainList: [],
editFormMethod: '',
formData: null,
}),
computed: {
searchedList() {
......@@ -244,6 +327,7 @@ export default{
// 가이드 페이지 이동
openGuide(guide) {
this.selectedIndex = guide
this.$router.push('/' + guide.id)
},
......@@ -252,10 +336,12 @@ export default{
await this.$axios.get('http://localhost:5000/test/index')
.then(res => {
this.rawIndexList = res.data
this.rawIndexList = JSON.parse(JSON.stringify(res.data));
let unclassifiedList = JSON.parse(JSON.stringify(res.data));
this.guideIndex = []
// 최상위 목차(depth==1) 추출(path에 슬래시 갯수가 1개)
this.rawIndexList.forEach(e => {
unclassifiedList.forEach(e => {
if(e.path.split('/').length === 2)
this.guideIndex.push(e)
});
......@@ -264,7 +350,7 @@ export default{
parentEl.children = []
// depth==2 추출
this.rawIndexList.forEach(rawEl => {
unclassifiedList.forEach(rawEl => {
if(rawEl.path !== parentEl.path && rawEl.path.includes(parentEl.path)){
parentEl.children.push(rawEl)
}
......@@ -284,6 +370,7 @@ export default{
this.activeNav()
this.loadingPageList = false
this.createEditModeList()
// this.activeGuideTreeIndex(this.guideIndex)
},
......@@ -358,50 +445,112 @@ export default{
},
// 가이드 생성
createGuide() {
if(!this.createGuideTitle || !this.createGuideContentKey) {
alert('내용 누락')
return
editOn(){
if(this.keyword === '열려라 문'){
this.editDialogActive = true
this.$store.state.editMode = true
}
},
createEditModeList(){
let unclassifiedList = JSON.parse(JSON.stringify(this.rawIndexList));
this.editModeList = []
this.editModeRemainList = []
// depth 이상치 확인
unclassifiedList.forEach(index => {
if(!index.depth || index.depth < 1){
this.editModeRemainList.push(index)
delete unclassifiedList[unclassifiedList.indexOf(index)]
}
})
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
// depth 1 리스트 생성
unclassifiedList.forEach(index => {
if(index.depth === 1){
this.editModeList.push(index)
delete unclassifiedList[unclassifiedList.indexOf(index)] // empty 처리
}
let guide = {
parentId:this.targetParentId,
title: this.createGuideTitle,
contentKey: this.createGuideContentKey
})
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
// depth 2 이상인 인덱스들 트리구조 생성
this.setEditModeChildren(this.editModeList, unclassifiedList)
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
this.editModeRemainList.push(...unclassifiedList)
},
setEditModeChildren(targetList, unclassifiedList){
targetList.forEach(el => {
el.children = []
unclassifiedList.forEach(ucsfEl => {
if(ucsfEl.path.indexOf(el.path) > -1 && ucsfEl.depth === el.depth + 1){
el.children.push(ucsfEl)
delete unclassifiedList[unclassifiedList.indexOf(ucsfEl)]
}
})
this.$axios.post('/guide/index', guide)
if(el.children.length > 0){
this.setEditModeChildren(el.children, unclassifiedList)
}
})
},
setEditTarget(item){
this.formData = item
this.editFormMethod = '수정'
},
setDepthValue(){
this.formData.depth = this.formData.path.split('/').length - 1
},
createIndex() {
const keys = ['locale', 'title' ,'order', 'path', 'depth']
if(keys.some(key => this.formData[key] === (undefined || null))){
alert('값을 모두 입력해주세요.')
return
}
this.$axios.post('http://localhost:5000/test', this.formData)
.then(res=>{
this.$refs.newPageDialog.dialog = false
this.getGuideIndex()
})
.catch(err=>{
console.log(err)
})
},
updateIndex() {
const keys = ['locale', 'title' ,'order', 'path', 'depth']
openCreateDialog(parentId, prefix) {
this.$refs.newPageDialog.dialog = true
this.createGuideTitle = ''
this.createGuideContentKey = prefix ? prefix + '/' : ''
this.targetParentId = parentId
},
if(keys.some(key => this.formData[key] === (undefined || null))){
alert('값을 모두 입력해주세요.')
return
}
removeGuide(guideId){
this.$axios.delete("/guide", {
this.$axios.put('http://localhost:5000/test/index', this.formData)
.then(res=>{
this.getGuideIndex()
})
.catch(err=>{
console.log(err)
})
},
removeIndex(){
this.$axios.delete("http://localhost:5000/test", {
params:{
guideId: guideId,
cascade:true
guideId: this.formData.id,
}
})
.then(res => {
this.formData = null
this.editFormMethod = ''
this.getGuideIndex()
})
.catch(err => {
console.log(err)
})
},
//가이드 네비게이션 이동
goGuideNav(position) {
window.scrollTo({top:position, behavior:'smooth'})
......@@ -488,13 +637,15 @@ export default{
for(let i=0; i<activeTargetPaths.length; ++i){
for(let j=0; j<guideList.length; ++j){
if(guideList[j].path === activeTargetPaths[i]){
this.selectedIndex = guideList[j]
guideList[j].active = true
guideList = guideList[j].children
}
break
}
}
}
},
},
mounted(){
this.getGuideIndex()
window.addEventListener("scroll", this.scrollEvent);
......@@ -528,6 +679,62 @@ export default{
</script>
<style lang="scss">
.v-dialog {
width: 70vw !important;
height: 80vh;
background-color: white;
.edit-dialog-container{
height: 100%;
position: relative;
.close-btn {
font-size: 26px;
position: absolute;
top: 20px;
right: 20px;
}
.col {
.v-subheader {
font-size: 1rem;
}
.v-list {
height: calc(100% - 48px);
background-color: rgba(0, 0, 0, 0.034);
border-radius: 10px;
overflow: auto;
.v-list-item {
&.active, &.v-list-item--active{
*{
font-weight: bold !important;
color: #1E88E5;
}
background-color: rgba(128,128,128,0.07) !important;
}
}
}
&:nth-child(2) {
display: flex;
flex-direction: column;
justify-content: space-between;
.v-list {
height: calc(100% - 48px);
}
}
.v-form {
height: calc(100% - 48px);
background-color: rgba(0, 0, 0, 0.034);
border-radius: 10px;
overflow: auto;
}
}
}
}
html.search-on {
@media screen and (max-width: 600px) {
overflow: hidden !important;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment