feat: init post list (#475)

This commit is contained in:
YangFong 2024-12-18 09:53:56 +08:00 committed by GitHub
parent c147b9dd22
commit 71de06633e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 251 additions and 10 deletions

View File

@ -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'
@ -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">

View File

@ -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>

View File

@ -52,12 +52,50 @@ export const useStore = defineStore(`store`, () => {
// 内容编辑器编辑器
const editor = ref<CodeMirror.EditorFromTextArea | null>(null)
// 编辑区域内容
// 预备弃用
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(`current_post_index`), 0)
const addPost = (title: string) => {
currentPostIndex.value = posts.value.push({
title,
content: DEFAULT_CONTENT,
}) - 1
}
const renamePost = (index: number, title: string) => {
posts.value[index].title = title
}
const delPost = (index: number) => {
posts.value.splice(index, 1)
currentPostIndex.value = 0
}
watch(currentPostIndex, () => {
toRaw(editor.value!).setValue(posts.value[currentPostIndex.value].content)
})
onMounted(() => {
// 迁移阶段,兼容之前的方案
if (editorContent.value !== DEFAULT_CONTENT) {
posts.value[currentPostIndex.value].content = editorContent.value
editorContent.value = DEFAULT_CONTENT
}
})
// 格式化文档
const formatContent = () => {
formatDoc((editor.value!).getValue()).then((doc) => {
editorContent.value = doc
posts.value[currentPostIndex.value].content = doc
toRaw(editor.value!).setValue(doc)
})
}
@ -421,7 +459,6 @@ export const useStore = defineStore(`store`, () => {
isOpenConfirmDialog,
resetStyleConfirm,
resetStyle,
editorContent,
cssContentConfig,
addCssContentTab,
@ -429,6 +466,12 @@ export const useStore = defineStore(`store`, () => {
setCssEditorValue,
tabChanged,
renameTab,
posts,
currentPostIndex,
addPost,
renamePost,
delPost,
isOpenPostSlider,
}
})

View File

@ -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 {
@ -15,6 +16,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import {
ContextMenu,
ContextMenuContent,
@ -38,7 +40,7 @@ import { toast } from 'vue-sonner'
const store = useStore()
const displayStore = useDisplayStore()
const { isDark, output, editor, editorContent } = storeToRefs(store)
const { isDark, output, editor } = storeToRefs(store)
const { isShowCssEditor } = storeToRefs(displayStore)
const {
@ -201,7 +203,7 @@ function initEditor() {
const editorDom = document.querySelector<HTMLTextAreaElement>(`#editor`)!
if (!editorDom.value) {
editorDom.value = editorContent.value
editorDom.value = store.posts[store.currentPostIndex].content
}
editor.value = CodeMirror.fromTextArea(editorDom, {
mode: `text/x-markdown`,
@ -248,7 +250,7 @@ function initEditor() {
clearTimeout(changeTimer.value)
changeTimer.value = setTimeout(() => {
onEditorRefresh()
editorContent.value = e.getValue()
store.posts[store.currentPostIndex].content = e.getValue()
}, 300)
})
@ -396,10 +398,11 @@ onMounted(() => {
@end-copy="endCopy"
/>
<main class="container-main flex-1">
<div class="container-main-section grid h-full border-1" :class="isShowCssEditor ? 'grid-cols-3' : 'grid-cols-2'">
<div class="container-main-section h-full flex border-1">
<PostSlider />
<div
ref="codeMirrorWrapper"
class="codeMirror-wrapper border-r-1"
class="codeMirror-wrapper flex-1 border-r-1"
:class="{
'order-1': !store.isEditOnLeft,
}"
@ -443,7 +446,7 @@ onMounted(() => {
id="preview"
ref="preview"
:span="isShowCssEditor ? 8 : 12"
class="preview-wrapper p-5"
class="preview-wrapper flex-1 p-5"
>
<div id="output-wrapper" :class="{ output_night: !backLight }">
<div class="preview border shadow-xl">
@ -457,7 +460,7 @@ onMounted(() => {
</div>
</div>
</div>
<CssEditor />
<CssEditor class="flex-1" />
</div>
</main>