Skip to content

Commit

Permalink
feat: supports rename and collapse
Browse files Browse the repository at this point in the history
  • Loading branch information
YangFong committed Dec 18, 2024
1 parent 2226808 commit a5bdae1
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 62 deletions.
8 changes: 7 additions & 1 deletion src/components/CodemirrorEditor/EditorHeader/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
} from '@/config'
import { useDisplayStore, useStore } from '@/stores'
import { mergeCss, solveWeChatImage } from '@/utils'
import { Moon, Paintbrush, Sun } from 'lucide-vue-next'
import { Moon, Paintbrush, PanelLeftClose, PanelLeftOpen, Sun } from 'lucide-vue-next'
import { storeToRefs } from 'pinia'
import { nextTick, ref, useTemplateRef } from 'vue'
import PickColors from 'vue-pick-colors'
Expand Down Expand Up @@ -219,6 +219,12 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
<HelpDropdown />
</Menubar>

<Button v-if="!store.isOpenPostSlider" variant="outline" @click="store.isOpenPostSlider = true" class="mr-2">
<PanelLeftOpen class="size-4" />
</Button>
<Button v-else variant="outline" @click="store.isOpenPostSlider = false" class="mr-2">
<PanelLeftClose class="size-4" />
</Button>
<Popover>
<PopoverTrigger>
<Button variant="outline">
Expand Down
189 changes: 189 additions & 0 deletions src/components/CodemirrorEditor/PostSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<script setup lang="ts">
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { useStore } from '@/stores'
import { Edit3, Ellipsis, Plus, Trash } from 'lucide-vue-next'
import { ref, watch } from 'vue'
import { toast } from 'vue-sonner'
const store = useStore()
const isOpen = ref(false)
const addPostInputVal = ref(``)
watch(isOpen, () => {
if (isOpen.value) {
addPostInputVal.value = ``
}
})
function addPost() {
if (addPostInputVal.value === ``) {
toast.error(`文章标题不可为空`)
return
}
store.addPost(addPostInputVal.value)
isOpen.value = false
toast.success(`文章新增成功`)
}
const editTarget = ref(-1)
const isOpenEditDialog = ref(false)
const renamePostInputVal = ref(``)
function startRenamePost(index: number) {
editTarget.value = index
renamePostInputVal.value = store.posts[index].title
isOpenEditDialog.value = true
}
function renamePost() {
if (renamePostInputVal.value === ``) {
toast.error(`文章标题不可为空`)
return
}
store.renamePost(editTarget.value, renamePostInputVal.value)
isOpenEditDialog.value = false
toast.success(`文章更名成功`)
}
const isOpenDelPostConfirmDialog = ref(false)
function startDelPost(index: number) {
editTarget.value = index
isOpenDelPostConfirmDialog.value = true
}
function delPost() {
store.delPost(editTarget.value)
isOpenDelPostConfirmDialog.value = false
toast.success(`文章删除成功`)
}
</script>

<template>
<div
class="overflow-hidden border-r bg-gray/20 transition-width dark:bg-gray/40"
:class="{
'w-0': !store.isOpenPostSlider,
'w-50': store.isOpenPostSlider,
}"
>
<nav class="space-y-1 h-full overflow-auto p-2">
<Dialog v-model:open="isOpen">
<DialogTrigger as-child>
<Button variant="outline" class="w-full" size="xs">
<Plus /> 新增文章
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>新增文章</DialogTitle>
<DialogDescription>
请输入文章名称
</DialogDescription>
</DialogHeader>
<Input v-model="addPostInputVal" />
<DialogFooter>
<Button @click="addPost()">
确 定
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<a
v-for="(post, index) in store.posts"
:key="post.title"
href="#"
:class="{
'bg-primary text-primary-foreground': store.currentPostIndex === index,
}"
class="hover:text-primary-foreground hover:bg-primary/90 dark:bg-muted dark:hover:bg-muted h-8 w-full inline-flex items-center justify-start gap-2 whitespace-nowrap rounded px-2 text-sm transition-colors dark:text-white dark:hover:text-white"
@click="store.currentPostIndex = index"
>
<span class="line-clamp-1">{{ post.title }}</span>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button size="xs" variant="ghost" class="ml-auto px-1.5">
<Ellipsis class="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click.stop="startRenamePost(index)">
<Edit3 class="mr-2 size-4" />
更名
</DropdownMenuItem>
<DropdownMenuItem @click.stop="startDelPost(index)">
<Trash class="mr-2 size-4" />
删除
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</a>
<!-- 重命名弹窗 -->
<Dialog v-model:open="isOpenEditDialog">
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>编辑文章名称</DialogTitle>
<DialogDescription>
请输入新的文章名称
</DialogDescription>
</DialogHeader>
<Input v-model="renamePostInputVal" />
<DialogFooter>
<Button variant="outline" @click="isOpenEditDialog = false">
取消
</Button>
<Button @click="renamePost()">
保存
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

<AlertDialog v-model:open="isOpenDelPostConfirmDialog">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>提示</AlertDialogTitle>
<AlertDialogDescription>
此操作将删除该文章,是否继续?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction @click="delPost()">
确认
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</nav>
</div>
</template>

<style scoped lang="less">
</style>
12 changes: 9 additions & 3 deletions src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,30 @@ export const useStore = defineStore(`store`, () => {
// 编辑区域内容
// 预备弃用
const editorContent = useStorage(`__editor_content`, DEFAULT_CONTENT)

const isOpenPostSlider = useStorage(addPrefix(`is_open_post_slider`), false)
// 文章列表
const posts = useStorage(addPrefix(`posts`), [{
title: `文章1`,
content: DEFAULT_CONTENT,
}])
// 当前文章
const currentPostIndex = useStorage(addPrefix(`currentPostIndex`), 0)
const currentPostIndex = useStorage(addPrefix(`current_post_index`), 0)

const addPost = (title: string) => {
currentPostIndex.value = posts.value.push({
title,
content: DEFAULT_CONTENT,
}) - 1
toast.success(`文章新增成功`)
}

const renamePost = (index: number, title: string) => {
posts.value[index].title = title
}

const delPost = (index: number) => {
posts.value.splice(index, 1)
currentPostIndex.value = 0
toast.success(`文章删除成功`)
}

watch(currentPostIndex, () => {
Expand Down Expand Up @@ -465,7 +469,9 @@ export const useStore = defineStore(`store`, () => {
posts,
currentPostIndex,
addPost,
renamePost,
delPost,
isOpenPostSlider,
}
})

Expand Down
61 changes: 3 additions & 58 deletions src/views/CodemirrorEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ComponentPublicInstance } from 'vue'
import CssEditor from '@/components/CodemirrorEditor/CssEditor.vue'
import EditorHeader from '@/components/CodemirrorEditor/EditorHeader/index.vue'
import InsertFormDialog from '@/components/CodemirrorEditor/InsertFormDialog.vue'
import PostSlider from '@/components/CodemirrorEditor/PostSlider.vue'
import UploadImgDialog from '@/components/CodemirrorEditor/UploadImgDialog.vue'
import RunLoading from '@/components/RunLoading.vue'
import {
Expand All @@ -15,6 +16,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import {
ContextMenu,
ContextMenuContent,
Expand All @@ -23,17 +25,6 @@ import {
ContextMenuShortcut,
ContextMenuTrigger,
} from '@/components/ui/context-menu'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { altKey, altSign, ctrlKey, shiftKey, shiftSign } from '@/config'
import { useDisplayStore, useStore } from '@/stores'
import {
Expand All @@ -43,7 +34,6 @@ import {
} from '@/utils'
import fileApi from '@/utils/file'
import CodeMirror from 'codemirror'
import { Plus, Trash } from 'lucide-vue-next'
import { storeToRefs } from 'pinia'
import { onMounted, ref, toRaw, watch } from 'vue'
import { toast } from 'vue-sonner'
Expand Down Expand Up @@ -397,16 +387,6 @@ onMounted(() => {
onEditorRefresh()
mdLocalToRemote()
})
const isOpen = ref(false)
const addPostInputVal = ref(``)
function addPost() {
store.addPost(addPostInputVal.value)
isOpen.value = false
addPostInputVal.value = ``
}
</script>

<template>
Expand All @@ -419,42 +399,7 @@ function addPost() {
/>
<main class="container-main flex-1">
<div class="container-main-section h-full flex border-1">
<nav class="space-y-1 w-50 border-r bg-gray/20 p-2 dark:bg-gray/40">
<Dialog v-model:open="isOpen">
<DialogTrigger as-child>
<Button variant="outline" class="w-full" size="xs">
<Plus /> 新增文章
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>新增文章</DialogTitle>
<DialogDescription>
请输入文章名称
</DialogDescription>
</DialogHeader>
<Input v-model="addPostInputVal" />
<DialogFooter>
<Button @click="addPost()">
确 定
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<a
v-for="(post, index) in store.posts"
:key="post.title"
href="#"
:class="{
'bg-primary text-primary-foreground': store.currentPostIndex === index,
}"
class="hover:text-primary-foreground hover:bg-primary/90 dark:bg-muted dark:hover:bg-muted h-8 w-full inline-flex items-center justify-start gap-2 whitespace-nowrap rounded px-4 text-sm transition-colors dark:text-white dark:hover:text-white"
@click="store.currentPostIndex = index"
>
<span>{{ post.title }}</span>
<Trash v-if="index == store.currentPostIndex" class="ml-auto size-4" @click.stop="store.delPost(index)" />
</a>
</nav>
<PostSlider />
<div
ref="codeMirrorWrapper"
class="codeMirror-wrapper flex-1 border-r-1"
Expand Down

0 comments on commit a5bdae1

Please sign in to comment.