This commit is contained in:
YangFong 2025-01-09 14:16:28 +00:00 committed by GitHub
commit 212bd04c61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 479 additions and 475 deletions

View File

@ -103,7 +103,7 @@ function tabChanged(tabName: string | number) {
<template>
<transition enter-active-class="bounceInRight">
<div v-show="displayStore.isShowCssEditor" class="cssEditor-wrapper order-1 h-full flex flex-col border-l-1">
<div v-show="displayStore.isShowCssEditor" class="cssEditor-wrapper h-full flex flex-col border-l-1">
<Tabs
v-model="store.cssContentConfig.active"
@update:model-value="tabChanged"

View File

@ -1,39 +1,16 @@
<script setup lang="ts">
import type { Format } from 'vue-pick-colors'
import { Toaster } from '@/components/ui/sonner'
import {
altSign,
codeBlockThemeOptions,
colorOptions,
ctrlKey,
ctrlSign,
fontFamilyOptions,
fontSizeOptions,
legendOptions,
shiftSign,
themeOptions,
} from '@/config'
import { useDisplayStore, useStore } from '@/stores'
import {
addPrefix,
processClipboardContent,
} from '@/utils'
import {
ChevronDownIcon,
Moon,
PanelLeftClose,
PanelLeftOpen,
Settings,
Sun,
} from 'lucide-vue-next'
import PickColors from 'vue-pick-colors'
import { useStore } from '@/stores'
import { addPrefix, processClipboardContent } from '@/utils'
import { ChevronDownIcon, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen } from 'lucide-vue-next'
const emit = defineEmits([
`addFormat`,
`formatContent`,
`startCopy`,
`endCopy`,
])
const emit = defineEmits([`addFormat`, `formatContent`, `startCopy`, `endCopy`])
const formatItems = [
{
@ -69,9 +46,8 @@ const formatItems = [
] as const
const store = useStore()
const displayStore = useDisplayStore()
const { isDark, isCiteStatus, isCountStatus, output, primaryColor } = storeToRefs(store)
const { isDark, isCiteStatus, isCountStatus, output, primaryColor, isOpenPostSlider } = storeToRefs(store)
const { toggleDark, editorRefresh, citeStatusChanged, countStatusChanged } = store
@ -123,439 +99,99 @@ function copy() {
})
}, 350)
}
function customStyle() {
displayStore.toggleShowCssEditor()
setTimeout(() => {
store.cssEditor!.refresh()
}, 50)
}
const pickColorsContainer = useTemplateRef<HTMLElement | undefined>(
`pickColorsContainer`,
)
const format = ref<Format>(`rgb`)
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
</script>
<template>
<header class="header-container h-15 flex items-center px-5">
<Menubar class="menubar mr-auto">
<FileDropdown />
<header class="header-container h-15 flex items-center justify-between px-5">
<div class="space-x-2 flex">
<Menubar class="menubar">
<FileDropdown />
<MenubarMenu>
<MenubarTrigger> 格式 </MenubarTrigger>
<MenubarContent class="w-60" align="start">
<MenubarCheckboxItem
v-for="{ label, kbd, emitArgs } in formatItems"
:key="label"
@click="
emitArgs[0] === 'addFormat'
? $emit(emitArgs[0], emitArgs[1])
: $emit(emitArgs[0])
"
>
{{ label }}
<MenubarShortcut>
<kbd
v-for="item in kbd"
:key="item"
class="mx-1 bg-gray-2 dark:bg-stone-9"
>
{{ item }}
</kbd>
</MenubarShortcut>
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarCheckboxItem
:checked="isCiteStatus"
@click="citeStatusChanged()"
>
微信外链转底部引用
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarCheckboxItem
:checked="isCountStatus"
@click="countStatusChanged()"
>
统计字数和阅读时间
</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
<EditDropdown />
<StyleDropdown />
<HelpDropdown />
</Menubar>
<Button
v-if="!store.isOpenPostSlider"
variant="outline"
class="mr-2"
@click="store.isOpenPostSlider = true"
>
<PanelLeftOpen class="size-4" />
</Button>
<Button
v-else
variant="outline"
class="mr-2"
@click="store.isOpenPostSlider = false"
>
<PanelLeftClose class="size-4" />
</Button>
<Popover>
<PopoverTrigger>
<Button variant="outline">
<Settings class="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent class="h-100 w-100 overflow-auto px-6" align="end">
<div class="space-y-4">
<div class="space-y-2">
<h2>主题</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in themeOptions"
:key="value"
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.theme === value,
}"
@click="store.themeChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>字体</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in fontFamilyOptions"
:key="value"
variant="outline"
class="w-full"
:class="{
'border-black dark:border-white': store.fontFamily === value,
}"
@click="store.fontChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>字号</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
v-for="{ value, desc } in fontSizeOptions"
:key="value"
variant="outline"
class="w-full"
:class="{
'border-black dark:border-white': store.fontSize === value,
}"
@click="store.sizeChanged(value)"
>
{{ desc }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>主题色</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in colorOptions"
:key="value"
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white':
store.primaryColor === value,
}"
@click="store.colorChanged(value)"
>
<span
class="mr-2 inline-block h-4 w-4 rounded-full"
:style="{
background: value,
}"
/>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>自定义主题色</h2>
<div ref="pickColorsContainer">
<PickColors
v-if="pickColorsContainer"
v-model:value="primaryColor"
show-alpha
:format="format"
:format-options="formatOptions"
:theme="store.isDark ? 'dark' : 'light'"
:popup-container="pickColorsContainer"
@change="store.colorChanged"
/>
</div>
</div>
<div class="space-y-2">
<h2>代码块主题</h2>
<div>
<Select
v-model="store.codeBlockTheme"
@update:model-value="store.codeBlockThemeChanged"
>
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="{ label, value } in codeBlockThemeOptions"
:key="label"
:value="value"
>
{{ label }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div class="space-y-2">
<h2>图注格式</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in legendOptions"
:key="value"
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.legend === value,
}"
@click="store.legendChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>Mac 代码块</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.isMacCodeBlock,
}"
@click="!store.isMacCodeBlock && store.macCodeBlockChanged()"
>
开启
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !store.isMacCodeBlock,
}"
@click="store.isMacCodeBlock && store.macCodeBlockChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>微信外链转底部引用</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.isCiteStatus,
}"
@click="!store.isCiteStatus && store.citeStatusChanged()"
>
开启
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !store.isCiteStatus,
}"
@click="store.isCiteStatus && store.citeStatusChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>统计字数和阅读时间</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.isCountStatus,
}"
@click="!store.isCountStatus && store.countStatusChanged()"
>
开启
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !store.isCountStatus,
}"
@click="store.isCountStatus && store.countStatusChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>段落首行缩进</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.isUseIndent,
}"
@click="!store.isUseIndent && store.useIndentChanged()"
>
开启
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !store.isUseIndent,
}"
@click="store.isUseIndent && store.useIndentChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>自定义 CSS 面板</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white':
displayStore.isShowCssEditor,
}"
@click="!displayStore.isShowCssEditor && customStyle()"
>
开启
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !displayStore.isShowCssEditor,
}"
@click="displayStore.isShowCssEditor && customStyle()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>编辑区位置</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': store.isEditOnLeft,
}"
@click="!store.isEditOnLeft && store.toggleEditOnLeft()"
>
左侧
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !store.isEditOnLeft,
}"
@click="store.isEditOnLeft && store.toggleEditOnLeft()"
>
右侧
</Button>
</div>
</div>
<div class="space-y-2">
<h2>模式</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': !isDark,
}"
@click="store.toggleDark(false)"
>
<Sun class="h-4 w-4" />
</Button>
<Button
class="w-full"
variant="outline"
:class="{
'border-black dark:border-white': isDark,
}"
@click="store.toggleDark(true)"
>
<Moon class="h-4 w-4" />
</Button>
</div>
</div>
<div class="space-y-2">
<h2>样式配置</h2>
<Button @click="store.resetStyleConfirm">
重置
</Button>
</div>
</div>
</PopoverContent>
</Popover>
<div
class="space-x-1 bg-background text-background-foreground mx-2 flex items-center border rounded-md"
>
<Button variant="ghost" class="shadow-none" @click="copy">
复制
</Button>
<Separator orientation="vertical" class="h-5" />
<DropdownMenu v-model="copyMode">
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="px-2 shadow-none">
<ChevronDownIcon class="text-secondary-foreground h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" :align-offset="-5" class="w-[200px]">
<DropdownMenuRadioGroup v-model="copyMode">
<DropdownMenuRadioItem value="txt">
公众号格式
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="html">
HTML 格式
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<MenubarMenu>
<MenubarTrigger> 格式 </MenubarTrigger>
<MenubarContent class="w-60" align="start">
<MenubarCheckboxItem
v-for="{ label, kbd, emitArgs } in formatItems" :key="label"
@click="emitArgs[0] === 'addFormat' ? $emit(emitArgs[0], emitArgs[1]) : $emit(emitArgs[0])"
>
{{ label }}
<MenubarShortcut>
<kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9">
{{ item }}
</kbd>
</MenubarShortcut>
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarCheckboxItem :checked="isCiteStatus" @click="citeStatusChanged()">
微信外链转底部引用
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarCheckboxItem
:checked="isCountStatus"
@click="countStatusChanged()"
>
统计字数和阅读时间
</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
<EditDropdown />
<StyleDropdown />
<HelpDropdown />
</Menubar>
</div>
<PostInfo />
<div class="space-x-2 flex">
<TooltipProvider :delay-duration="200">
<Tooltip>
<TooltipTrigger as-child>
<Button variant="outline" @click="isOpenPostSlider = !isOpenPostSlider">
<PanelLeftOpen v-show="!isOpenPostSlider" class="size-4" />
<PanelLeftClose v-show="isOpenPostSlider" class="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="left">
{{ isOpenPostSlider ? "关闭" : "展开" }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Toaster rich-colors position="top-center" />
<div class="space-x-1 bg-background text-background-foreground mx-2 flex items-center border rounded-md">
<Button variant="ghost" class="shadow-none" @click="copy">
复制
</Button>
<Separator orientation="vertical" class="h-5" />
<DropdownMenu v-model="copyMode">
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="px-2 shadow-none">
<ChevronDownIcon class="text-secondary-foreground h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
:align-offset="-5"
class="w-[200px]"
>
<DropdownMenuRadioGroup v-model="copyMode">
<DropdownMenuRadioItem value="txt">
公众号格式
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="html">
HTML 格式
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<PostInfo />
<Button variant="outline" @click="store.isOpenRightSlider = !store.isOpenRightSlider">
<PanelRightOpen v-show="!store.isOpenRightSlider" class="size-4" />
<PanelRightClose v-show="store.isOpenRightSlider" class="size-4" />
</Button>
<Toaster rich-colors position="top-center" />
</div>
</header>
</template>

View File

@ -57,13 +57,19 @@ function delPost() {
<template>
<div
class="overflow-hidden border-r bg-gray/20 transition-width dark:bg-gray/40"
class="overflow-hidden bg-gray/20 transition-width duration-300 dark:bg-gray/40"
:class="{
'w-0': !store.isOpenPostSlider,
'w-50': store.isOpenPostSlider,
}"
>
<nav class="space-y-1 h-full overflow-auto p-2">
<nav
class="space-y-1 h-full overflow-auto p-2 transition-transform"
:class="{
'translate-x-100': store.isOpenPostSlider,
'-translate-x-full': !store.isOpenPostSlider,
}"
>
<Dialog v-model:open="isOpen">
<DialogTrigger as-child>
<Button variant="outline" class="w-full" size="xs">
@ -86,10 +92,7 @@ function delPost() {
</DialogContent>
</Dialog>
<a
v-for="(post, index) in store.posts"
:key="post.title"
href="#"
:class="{
v-for="(post, index) in store.posts" :key="post.title" href="#" :class="{
'bg-primary text-primary-foreground shadow-lg border-2 border-primary': store.currentPostIndex === index,
'dark:bg-primary-dark dark:text-primary-foreground-dark dark:border-primary-dark': store.currentPostIndex === index,
}"

View File

@ -0,0 +1,276 @@
<script setup lang="ts">
import {
codeBlockThemeOptions,
colorOptions,
fontFamilyOptions,
fontSizeOptions,
legendOptions,
themeOptions,
} from '@/config'
import { useDisplayStore, useStore } from '@/stores'
import { Moon, Sun } from 'lucide-vue-next'
import PickColors, { type Format } from 'vue-pick-colors'
const store = useStore()
const displayStore = useDisplayStore()
const { isDark, primaryColor } = storeToRefs(store)
function customStyle() {
displayStore.toggleShowCssEditor()
setTimeout(() => {
store.cssEditor!.refresh()
}, 50)
}
const isOpen = ref(false)
const addPostInputVal = ref(``)
watch(isOpen, () => {
if (isOpen.value) {
addPostInputVal.value = ``
}
})
const pickColorsContainer = useTemplateRef<HTMLElement | undefined>(`pickColorsContainer`)
const format = ref<Format>(`rgb`)
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
</script>
<template>
<div
class="overflow-hidden bg-gray/20 transition-width duration-300 dark:bg-gray/40"
:class="{
'w-0': !store.isOpenRightSlider,
'w-100': store.isOpenRightSlider,
}"
>
<div
class="space-y-4 h-full overflow-auto p-4 transition-transform" :class="{
'translate-x-0': store.isOpenRightSlider,
'translate-x-full': !store.isOpenRightSlider,
}"
>
<div class="space-y-2">
<h2>主题</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in themeOptions" :key="value" class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.theme === value,
}" @click="store.themeChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>字体</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in fontFamilyOptions" :key="value" variant="outline" class="w-full"
:class="{ 'border-black dark:border-white': store.fontFamily === value }"
@click="store.fontChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>字号</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
v-for="{ value, desc } in fontSizeOptions" :key="value" variant="outline" class="w-full" :class="{
'border-black dark:border-white': store.fontSize === value,
}" @click="store.sizeChanged(value)"
>
{{ desc }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>主题色</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in colorOptions" :key="value" class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.primaryColor === value,
}" @click="store.colorChanged(value)"
>
<span
class="mr-2 inline-block h-4 w-4 rounded-full" :style="{
background: value,
}"
/>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>自定义主题色</h2>
<div ref="pickColorsContainer">
<PickColors
v-if="pickColorsContainer"
v-model:value="primaryColor"
show-alpha :format="format"
:format-options="formatOptions"
:theme="store.isDark ? 'dark' : 'light'"
:popup-container="pickColorsContainer"
@change="store.colorChanged"
/>
</div>
</div>
<div class="space-y-2">
<h2>代码块主题</h2>
<div>
<Select v-model="store.codeBlockTheme" @update:model-value="store.codeBlockThemeChanged">
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="{ label, value } in codeBlockThemeOptions" :key="label" :value="value">
{{ label }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div class="space-y-2">
<h2>图注格式</h2>
<div class="grid grid-cols-3 justify-items-center gap-2">
<Button
v-for="{ label, value } in legendOptions" :key="value" class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.legend === value,
}" @click="store.legendChanged(value)"
>
{{ label }}
</Button>
</div>
</div>
<div class="space-y-2">
<h2>Mac 代码块</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.isMacCodeBlock,
}" @click="!store.isMacCodeBlock && store.macCodeBlockChanged()"
>
开启
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !store.isMacCodeBlock,
}" @click="store.isMacCodeBlock && store.macCodeBlockChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>微信外链转底部引用</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.isCiteStatus,
}" @click="!store.isCiteStatus && store.citeStatusChanged()"
>
开启
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !store.isCiteStatus,
}" @click="store.isCiteStatus && store.citeStatusChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>段落首行缩进</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.isUseIndent,
}" @click="!store.isUseIndent && store.useIndentChanged()"
>
开启
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !store.isUseIndent,
}" @click="store.isUseIndent && store.useIndentChanged()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>自定义 CSS 面板</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': displayStore.isShowCssEditor,
}" @click="!displayStore.isShowCssEditor && customStyle()"
>
开启
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !displayStore.isShowCssEditor,
}" @click="displayStore.isShowCssEditor && customStyle()"
>
关闭
</Button>
</div>
</div>
<div class="space-y-2">
<h2>编辑区位置</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': store.isEditOnLeft,
}" @click="!store.isEditOnLeft && store.toggleEditOnLeft()"
>
左侧
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !store.isEditOnLeft,
}" @click="store.isEditOnLeft && store.toggleEditOnLeft()"
>
右侧
</Button>
</div>
</div>
<div class="space-y-2">
<h2>模式</h2>
<div class="grid grid-cols-5 justify-items-center gap-2">
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': !isDark,
}" @click="store.toggleDark(false)"
>
<Sun class="h-4 w-4" />
</Button>
<Button
class="w-full" variant="outline" :class="{
'border-black dark:border-white': isDark,
}" @click="store.toggleDark(true)"
>
<Moon class="h-4 w-4" />
</Button>
</div>
</div>
<div class="space-y-2">
<h2>样式配置</h2>
<Button @click="store.resetStyleConfirm">
重置
</Button>
</div>
</div>
</div>
</template>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<TooltipRootProps>()
const emits = defineEmits<TooltipRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<TooltipRoot v-bind="forwarded">
<slot />
</TooltipRoot>
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { TooltipContent, type TooltipContentEmits, type TooltipContentProps, TooltipPortal, useForwardPropsEmits } from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes[`class`] }>(), {
sideOffset: 4,
})
const emits = defineEmits<TooltipContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<TooltipPortal>
<TooltipContent v-bind="{ ...forwarded, ...$attrs }" :class="cn('z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)">
<slot />
</TooltipContent>
</TooltipPortal>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipProvider, type TooltipProviderProps } from 'radix-vue'
const props = defineProps<TooltipProviderProps>()
</script>
<template>
<TooltipProvider v-bind="props">
<slot />
</TooltipProvider>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipTrigger, type TooltipTriggerProps } from 'radix-vue'
const props = defineProps<TooltipTriggerProps>()
</script>
<template>
<TooltipTrigger v-bind="props">
<slot />
</TooltipTrigger>
</template>

View File

@ -0,0 +1,4 @@
export { default as Tooltip } from './Tooltip.vue'
export { default as TooltipContent } from './TooltipContent.vue'
export { default as TooltipProvider } from './TooltipProvider.vue'
export { default as TooltipTrigger } from './TooltipTrigger.vue'

View File

@ -56,6 +56,8 @@ export const useStore = defineStore(`store`, () => {
// 预备弃用
const editorContent = useStorage(`__editor_content`, DEFAULT_CONTENT)
const isOpenRightSlider = useStorage(addPrefix(`is_open_right_slider`), false)
const isOpenPostSlider = useStorage(addPrefix(`is_open_post_slider`), false)
// 文章列表
const posts = useStorage(addPrefix(`posts`), [{
@ -492,6 +494,7 @@ export const useStore = defineStore(`store`, () => {
renamePost,
delPost,
isOpenPostSlider,
isOpenRightSlider,
}
})

View File

@ -13,7 +13,6 @@ import CodeMirror from 'codemirror'
const store = useStore()
const displayStore = useDisplayStore()
const { isDark, output, editor, readingTime } = storeToRefs(store)
const { isShowCssEditor } = storeToRefs(displayStore)
const {
editorRefresh,
@ -363,18 +362,30 @@ onMounted(() => {
<template>
<div ref="container" class="container flex flex-col">
<EditorHeader @add-format="addFormat" @format-content="formatContent" @start-copy="startCopy" @end-copy="endCopy" />
<main class="container-main flex-1">
<div class="container-main-section h-full flex border-1">
<EditorHeader
@add-format="addFormat"
@format-content="formatContent"
@start-copy="startCopy"
@end-copy="endCopy"
/>
<main class="container-main flex flex-1 flex-col">
<div class="container-main-section border-radius-10 relative flex flex-1 overflow-hidden border-1">
<PostSlider />
<div
ref="codeMirrorWrapper" class="codeMirror-wrapper flex-1 border-r-1" :class="{
'order-1': !store.isEditOnLeft,
ref="codeMirrorWrapper"
class="codeMirror-wrapper flex-1"
:class="{
'order-1 border-l': !store.isEditOnLeft,
'border-r': store.isEditOnLeft,
}"
>
<ContextMenu>
<ContextMenuTrigger>
<textarea id="editor" type="textarea" placeholder="Your markdown text here." />
<textarea
id="editor"
type="textarea"
placeholder="Your markdown text here."
/>
</ContextMenuTrigger>
<ContextMenuContent class="w-64">
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
@ -403,9 +414,13 @@ onMounted(() => {
</ContextMenuContent>
</ContextMenu>
</div>
<div id="preview" ref="preview" :span="isShowCssEditor ? 8 : 12" class="preview-wrapper flex-1 p-5">
<div
id="preview"
ref="preview"
class="preview-wrapper flex-1 p-5"
>
<div id="output-wrapper" :class="{ output_night: !backLight }">
<div class="preview border shadow-xl">
<div class="preview border-x-1 shadow-xl">
<section id="output" v-html="output" />
<div v-if="isCoping" class="loading-mask">
<div class="loading-mask-box">
@ -416,9 +431,10 @@ onMounted(() => {
</div>
</div>
</div>
<CssEditor class="flex-1" />
<CssEditor class="order-2 flex-1" />
<RightSlider class="order-2" />
</div>
<footer class="flex flex-1 justify-end pr-5 text-[12px]">
<footer class="h-[30px] flex items-center justify-end text-[12px]">
字数 {{ readingTime?.words }} 阅读大约需 {{ Math.ceil(readingTime?.minutes ?? 0) }} 分钟
</footer>
@ -461,8 +477,7 @@ onMounted(() => {
.container-main {
overflow: hidden;
padding: 20px;
padding-top: 0;
padding: 0 20px;
}
#output-wrapper {