style: update dialog (#402)

This commit is contained in:
YangFong 2024-09-16 00:01:54 +08:00 committed by GitHub
parent 442beb8053
commit d3a7d08f9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 1525 additions and 862 deletions

View File

@ -5,66 +5,63 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 0 0% 3.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 0 0% 3.9%;
--border: 214.3 31.8% 91.4%; --popover: 0 0% 100%;
--input: 214.3 31.8% 91.4%; --popover-foreground: 0 0% 3.9%;
--primary: 222.2 47.4% 11.2%; --primary: 0 0% 9%;
--primary-foreground: 210 40% 98%; --primary-foreground: 0 0% 98%;
--secondary: 210 40% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 0 0% 9%;
--accent: 210 40% 96.1%; --muted: 0 0% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 98%;
--ring: 222.2 84% 4.9%;
--border:0 0% 89.8%;
--input:0 0% 89.8%;
--ring:0 0% 3.9%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background:0 0% 3.9%;
--foreground: 210 40% 98%; --foreground:0 0% 98%;
--muted: 217.2 32.6% 17.5%; --card:0 0% 3.9%;
--muted-foreground: 215 20.2% 65.1%; --card-foreground:0 0% 98%;
--popover: 222.2 84% 4.9%; --popover:0 0% 3.9%;
--popover-foreground: 210 40% 98%; --popover-foreground:0 0% 98%;
--card: 222.2 84% 4.9%; --primary:0 0% 98%;
--card-foreground: 210 40% 98%; --primary-foreground:0 0% 9%;
--border: 217.2 32.6% 17.5%; --secondary:0 0% 14.9%;
--input: 217.2 32.6% 17.5%; --secondary-foreground:0 0% 98%;
--primary: 210 40% 98%; --muted:0 0% 14.9%;
--primary-foreground: 222.2 47.4% 11.2%; --muted-foreground:0 0% 63.9%;
--secondary: 217.2 32.6% 17.5%; --accent:0 0% 14.9%;
--secondary-foreground: 210 40% 98%; --accent-foreground:0 0% 98%;
--accent: 217.2 32.6% 17.5%; --destructive:0 62.8% 30.6%;
--accent-foreground: 210 40% 98%; --destructive-foreground:0 0% 98%;
--destructive: 0 62.8% 30.6%; --border:0 0% 14.9%;
--destructive-foreground: 210 40% 98%; --input:0 0% 14.9%;
--ring:0 0% 83.1%;
--ring: 212.7 26.8% 83.9%;
} }
} }

View File

@ -71,7 +71,6 @@ section {
justify-content: center; justify-content: center;
padding: 0; padding: 0;
overflow-y: scroll; overflow-y: scroll;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
} }
.hint { .hint {
@ -87,8 +86,6 @@ section {
font-size: 14px; font-size: 14px;
box-sizing: border-box; box-sizing: border-box;
outline: none; outline: none;
color: var(--el-text-color-regular);
box-shadow: var(--el-box-shadow);
} }
.preview table { .preview table {

View File

@ -1,35 +1,13 @@
@nightBgColor: #333333; @nightPreviewColor: #191919;
@nightPreviewColor: #1e1e1e; @nightCodeMirrorColor: #191919;
@nightHeaderColor: #3c3c3c;
@nightCodeMirrorColor: #1e1e1e;
@nightActiveCodeMirrorColor: gray; @nightActiveCodeMirrorColor: gray;
@nightFontColor: gray; @nightFontColor: gray;
@nightLinkColor: #8e9eb9; @nightLinkColor: #8e9eb9;
@nightLinkTextColor: #84868b; @nightLinkTextColor: #84868b;
@nightWhiteColor: #f0f0f0;
@nightButtonBg: #1e1e1e;
@nightButtonHoverColor: #84868b;
@nightLineColor: #84868b; @nightLineColor: #84868b;
.dark { .dark {
.container { .container {
background-color: @nightBgColor;
.CodeMirror {
caret-color: @nightFontColor;
color: @nightFontColor;
background-color: @nightCodeMirrorColor;
box-shadow: inset 0 0 0 1px rgba(100, 37, 37, 0.102);
.CodeMirror-cursor {
border-left: 1px solid @nightLineColor;
}
.CodeMirror-activeline-background {
background-color: #3e3e3e!important;
}
}
.output_night { .output_night {
.preview { .preview {
background-color: @nightPreviewColor; background-color: @nightPreviewColor;
@ -46,20 +24,6 @@
} }
} }
.cm-s-xq-light span.cm-variable-2,
.cm-s-xq-light .CodeMirror-activeline-background {
background-color: transparent;
}
.cm-s-xq-light span.cm-string {
color: @nightLinkColor;
}
.cm-s-xq-light span.cm-link {
color: @nightLinkTextColor;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
background-color: @nightCodeMirrorColor; background-color: @nightCodeMirrorColor;
} }
@ -69,7 +33,6 @@
.CodeMirror { .CodeMirror {
padding-bottom: 0; padding-bottom: 0;
height: 100% !important; height: 100% !important;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
font-size: 14px; font-size: 14px;
font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue', font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue',
sans-serif !important; sans-serif !important;

View File

@ -67,7 +67,7 @@ function handleTabsEdit(targetName, action) {
<template> <template>
<transition enter-active-class="bounceInRight"> <transition enter-active-class="bounceInRight">
<el-col v-show="store.isShowCssEditor" :span="8" class="cssEditor-wrapper order-1 h-full flex flex-col"> <el-col v-show="store.isShowCssEditor" :span="8" class="cssEditor-wrapper order-1 h-full flex flex-col border-l-1">
<el-tabs <el-tabs
v-model="store.cssContentConfig.active" v-model="store.cssContentConfig.active"
type="border-card" type="border-card"

View File

@ -1,4 +1,12 @@
<script setup> <script setup>
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
const props = defineProps({ const props = defineProps({
visible: { visible: {
type: Boolean, type: Boolean,
@ -8,6 +16,12 @@ const props = defineProps({
const emit = defineEmits([`close`]) const emit = defineEmits([`close`])
function onUpdate(val) {
if (!val) {
emit(`close`)
}
}
const links = [ const links = [
{ label: `GitHub 仓库`, url: `https://github.com/doocs/md` }, { label: `GitHub 仓库`, url: `https://github.com/doocs/md` },
{ label: `Gitee 仓库`, url: `https://gitee.com/doocs/md` }, { label: `Gitee 仓库`, url: `https://gitee.com/doocs/md` },
@ -20,14 +34,11 @@ function onRedirect(url) {
</script> </script>
<template> <template>
<el-dialog <Dialog :open="props.visible" @update:open="onUpdate">
title="关于" <DialogContent>
class="about__dialog" <DialogHeader>
:model-value="props.visible" <DialogTitle>关于</DialogTitle>
width="520" </DialogHeader>
center
@close="emit('close')"
>
<div class="text-center"> <div class="text-center">
<h3>一款高度简洁的微信 Markdown 编辑器</h3> <h3>一款高度简洁的微信 Markdown 编辑器</h3>
<p>扫码关注公众号 Doocs原创技术文章第一时间推送</p> <p>扫码关注公众号 Doocs原创技术文章第一时间推送</p>
@ -38,16 +49,15 @@ function onRedirect(url) {
style="width: 40%" style="width: 40%"
> >
</div> </div>
<template #footer> <DialogFooter class="sm:justify-evenly">
<el-button <Button
v-for="link in links" v-for="link in links"
:key="link.url" :key="link.url"
type="primary"
plain
@click="onRedirect(link.url)" @click="onRedirect(link.url)"
> >
{{ link.label }} {{ link.label }}
</el-button> </Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>

View File

@ -1,49 +1,27 @@
<script setup> <script setup>
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useStore } from '@/stores' import { useStore } from '@/stores'
const props = defineProps([`isOpen`, `clickTrigger`, `openDropdown`, `updateOpen`])
const store = useStore() const store = useStore()
const { const { toggleShowInsertFormDialog, toggleShowUploadImgDialog } = store
toggleShowInsertFormDialog,
toggleShowUploadImgDialog,
} = store
</script> </script>
<template> <template>
<DropdownMenu :open="props.isOpen" @update:open="props.updateOpen"> <MenubarMenu>
<DropdownMenuTrigger <MenubarTrigger> 编辑 </MenubarTrigger>
class="flex items-center p-2 px-4 hover:bg-gray-2 dark:hover:bg-stone-9" <MenubarContent align="start">
:class="{ <MenubarItem @click="toggleShowUploadImgDialog()">
'bg-gray-2': props.isOpen,
'dark:bg-stone-9': props.isOpen,
}"
@click="props.clickTrigger()"
@mouseenter="props.openDropdown()"
>
编辑
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem @click="toggleShowUploadImgDialog()">
<el-icon class="mr-2 h-4 w-4"> <el-icon class="mr-2 h-4 w-4">
<ElIconUpload /> <ElIconUpload />
</el-icon> </el-icon>
上传图片 上传图片
</DropdownMenuItem> </MenubarItem>
<DropdownMenuItem @click="toggleShowInsertFormDialog()"> <MenubarItem @click="toggleShowInsertFormDialog()">
<el-icon class="mr-2 h-4 w-4"> <el-icon class="mr-2 h-4 w-4">
<ElIconGrid /> <ElIconGrid />
</el-icon> </el-icon>
插入表格 插入表格
</DropdownMenuItem> </MenubarItem>
</DropdownMenuContent> </MenubarContent>
</DropdownMenu> </MenubarMenu>
</template> </template>

View File

@ -1,18 +1,8 @@
<script setup> <script setup>
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useStore } from '@/stores' import { useStore } from '@/stores'
const props = defineProps([`isOpen`, `clickTrigger`, `openDropdown`, `updateOpen`])
const store = useStore() const store = useStore()
const { const {
@ -30,51 +20,43 @@ const {
</script> </script>
<template> <template>
<DropdownMenu :open="props.isOpen" @update:open="props.updateOpen"> <MenubarMenu>
<DropdownMenuTrigger <MenubarTrigger>
class="flex items-center p-2 px-4 hover:bg-gray-2 dark:hover:bg-stone-9"
:class="{
'bg-gray-2': props.isOpen,
'dark:bg-stone-9': props.isOpen,
}"
@click="props.clickTrigger()"
@mouseenter="props.openDropdown()"
>
文件 文件
</DropdownMenuTrigger> </MenubarTrigger>
<DropdownMenuContent align="start"> <MenubarContent align="start">
<DropdownMenuItem @click="importMarkdownContent()"> <MenubarItem @click="importMarkdownContent()">
<el-icon class="mr-2 h-4 w-4"> <el-icon class="mr-2 h-4 w-4">
<ElIconUpload /> <ElIconUpload />
</el-icon> </el-icon>
导入 .md 导入 .md
</DropdownMenuItem> </MenubarItem>
<DropdownMenuItem @click="exportEditorContent2MD()"> <MenubarItem @click="exportEditorContent2MD()">
<el-icon class="mr-2 h-4 w-4"> <el-icon class="mr-2 h-4 w-4">
<ElIconDownload /> <ElIconDownload />
</el-icon> </el-icon>
导出 .md 导出 .md
</DropdownMenuItem> </MenubarItem>
<DropdownMenuItem @click="exportEditorContent2HTML()"> <MenubarItem @click="exportEditorContent2HTML()">
<el-icon class="mr-2 h-4 w-4"> <el-icon class="mr-2 h-4 w-4">
<ElIconDocument /> <ElIconDocument />
</el-icon> </el-icon>
导出 .html 导出 .html
</DropdownMenuItem> </MenubarItem>
<DropdownMenuSeparator /> <MenubarSeparator />
<DropdownMenuItem @click="toggleDark()"> <MenubarItem @click="toggleDark()">
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isDark }"> <el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isDark }">
<ElIconCheck /> <ElIconCheck />
</el-icon> </el-icon>
深色模式 深色模式
</DropdownMenuItem> </MenubarItem>
<DropdownMenuSeparator /> <MenubarSeparator />
<DropdownMenuItem @click="toggleEditOnLeft()"> <MenubarItem @click="toggleEditOnLeft()">
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isEditOnLeft }"> <el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isEditOnLeft }">
<ElIconCheck /> <ElIconCheck />
</el-icon> </el-icon>
左侧编辑 左侧编辑
</DropdownMenuItem> </MenubarItem>
</DropdownMenuContent> </MenubarContent>
</DropdownMenu> </MenubarMenu>
</template> </template>

View File

@ -3,41 +3,21 @@ import { ref } from 'vue'
import AboutDialog from './AboutDialog.vue' import AboutDialog from './AboutDialog.vue'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
const props = defineProps([`isOpen`, `clickTrigger`, `openDropdown`, `updateOpen`])
const aboutDialogVisible = ref(false) const aboutDialogVisible = ref(false)
</script> </script>
<template> <template>
<DropdownMenu :open="props.isOpen" @update:open="props.updateOpen"> <MenubarMenu>
<DropdownMenuTrigger <MenubarTrigger>
class="flex items-center p-2 px-4 hover:bg-gray-2 dark:hover:bg-stone-9"
:class="{
'bg-gray-2': props.isOpen,
'dark:bg-stone-9': props.isOpen,
}"
@click="props.clickTrigger()"
@mouseenter="props.openDropdown()"
>
帮助 帮助
</DropdownMenuTrigger> </MenubarTrigger>
<DropdownMenuContent align="start"> <MenubarContent align="start">
<DropdownMenuItem @click="aboutDialogVisible = true"> <MenubarItem @click="aboutDialogVisible = true">
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
<span>关于</span> <span>关于</span>
</DropdownMenuItem> </MenubarItem>
</DropdownMenuContent> </MenubarContent>
</DropdownMenu> </MenubarMenu>
<AboutDialog <AboutDialog :visible="aboutDialogVisible" @close="aboutDialogVisible = false" />
:visible="aboutDialogVisible"
@close="aboutDialogVisible = false"
/>
</template> </template>

View File

@ -1,6 +1,13 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useStore } from '@/stores' import { useStore } from '@/stores'
@ -48,6 +55,12 @@ function post() {
content: form.value.content || form.value.auto.content, content: form.value.content || form.value.auto.content,
}) })
} }
function onUpdate(val) {
if (!val) {
dialogVisible.value = false
}
}
</script> </script>
<template> <template>
@ -55,33 +68,24 @@ function post() {
发布 发布
</Button> </Button>
<el-dialog <Dialog :open="dialogVisible" @update:open="onUpdate">
title="发布" <DialogContent>
:model-value="dialogVisible" <DialogHeader>
@close="dialogVisible = false" <DialogTitle>发布</DialogTitle>
> </DialogHeader>
<el-alert <el-alert
class="mb-4" class="mb-4"
title="注:此功能由第三方浏览器插件支持,本平台不保证安全性。" title="注:此功能由第三方浏览器插件支持,本平台不保证安全性。"
type="info" type="info"
show-icon show-icon
/> />
<el-form <el-form class="postInfo" label-width="50" :model="form">
class="postInfo"
label-width="50"
:model="form"
>
<el-form-item label="封面"> <el-form-item label="封面">
<el-input <el-input v-model="form.thumb" placeholder="自动提取第一张图" />
v-model="form.thumb"
placeholder="自动提取第一张图"
/>
</el-form-item> </el-form-item>
<el-form-item label="标题"> <el-form-item label="标题">
<el-input <el-input v-model="form.title" placeholder="自动提取第一个标题" />
v-model="form.title"
placeholder="自动提取第一个标题"
/>
</el-form-item> </el-form-item>
<el-form-item label="描述"> <el-form-item label="描述">
<el-input <el-input
@ -93,13 +97,14 @@ function post() {
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <DialogFooter>
<el-button @click="dialogVisible = false"> <Button variant="outline" @click="dialogVisible = false">
</el-button> </Button>
<el-button type="primary" @click="post"> <Button @click="post">
</el-button> </Button>
</template> </DialogFooter>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>

View File

@ -4,25 +4,22 @@ import { storeToRefs } from 'pinia'
import StyleOptionMenu from './StyleOptionMenu.vue' import StyleOptionMenu from './StyleOptionMenu.vue'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { import {
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
HoverCardTrigger, HoverCardTrigger,
} from '@/components/ui/hover-card' } from '@/components/ui/hover-card'
import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, themeOptions } from '@/config' import {
codeBlockThemeOptions,
colorOptions,
fontFamilyOptions,
fontSizeOptions,
legendOptions,
themeOptions,
} from '@/config'
import { useStore } from '@/stores' import { useStore } from '@/stores'
const props = defineProps([`isOpen`, `clickTrigger`, `openDropdown`, `updateOpen`])
const store = useStore() const store = useStore()
const { const {
@ -69,23 +66,28 @@ function customStyle() {
</script> </script>
<template> <template>
<DropdownMenu :open="props.isOpen" @update:open="props.updateOpen"> <MenubarMenu>
<DropdownMenuTrigger <MenubarTrigger> 样式 </MenubarTrigger>
class="flex items-center p-2 px-4 hover:bg-gray-2 dark:hover:bg-stone-9" <MenubarContent class="w-56" align="start">
:class="{ <StyleOptionMenu
'bg-gray-2': props.isOpen, title="主题"
'dark:bg-stone-9': props.isOpen, :options="themeOptions"
}" :current="theme"
@click="props.clickTrigger()" :change="themeChanged"
@mouseenter="props.openDropdown()" />
> <MenubarSeparator />
样式 <StyleOptionMenu
</DropdownMenuTrigger> title="字体"
<DropdownMenuContent class="w-56" align="start"> :options="fontFamilyOptions"
<StyleOptionMenu title="主题" :options="themeOptions" :current="theme" :change="themeChanged" /> :current="fontFamily"
<DropdownMenuSeparator /> :change="fontChanged"
<StyleOptionMenu title="字体" :options="fontFamilyOptions" :current="fontFamily" :change="fontChanged" /> />
<StyleOptionMenu title="字号" :options="fontSizeOptions" :current="fontSize" :change="sizeChanged" /> <StyleOptionMenu
title="字号"
:options="fontSizeOptions"
:current="fontSize"
:change="sizeChanged"
/>
<StyleOptionMenu <StyleOptionMenu
title="主题色" title="主题色"
:options="colorOptions" :options="colorOptions"
@ -98,9 +100,14 @@ function customStyle() {
:current="codeBlockTheme" :current="codeBlockTheme"
:change="codeBlockThemeChanged" :change="codeBlockThemeChanged"
/> />
<StyleOptionMenu title="图注格式" :options="legendOptions" :current="legend" :change="legendChanged" /> <StyleOptionMenu
<DropdownMenuSeparator /> title="图注格式"
<DropdownMenuItem @click.self.prevent="showPicker"> :options="legendOptions"
:current="legend"
:change="legendChanged"
/>
<MenubarSeparator />
<MenubarItem @click.self.prevent="showPicker">
<HoverCard :open-delay="100"> <HoverCard :open-delay="100">
<HoverCardTrigger class="w-full flex"> <HoverCardTrigger class="w-full flex">
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
@ -131,23 +138,23 @@ function customStyle() {
@change="colorChanged" @change="colorChanged"
@click="showPicker" @click="showPicker"
/> --> /> -->
</DropdownMenuItem> </MenubarItem>
<DropdownMenuItem @click="customStyle"> <MenubarItem @click="customStyle">
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
自定义 CSS 自定义 CSS
</DropdownMenuItem> </MenubarItem>
<DropdownMenuSeparator /> <MenubarSeparator />
<DropdownMenuItem @click="macCodeBlockChanged"> <MenubarItem @click="macCodeBlockChanged">
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isMacCodeBlock }"> <el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isMacCodeBlock }">
<ElIconCheck /> <ElIconCheck />
</el-icon> </el-icon>
Mac 代码块 Mac 代码块
</DropdownMenuItem> </MenubarItem>
<DropdownMenuSeparator /> <MenubarSeparator />
<DropdownMenuItem divided @click="resetStyleConfirm"> <MenubarItem divided @click="resetStyleConfirm">
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
重置 重置
</DropdownMenuItem> </MenubarItem>
</DropdownMenuContent> </MenubarContent>
</DropdownMenu> </MenubarMenu>
</template> </template>

View File

@ -1,12 +1,10 @@
<script setup> <script setup>
import { import {
DropdownMenuItem, MenubarItem,
DropdownMenuPortal, MenubarSub,
DropdownMenuShortcut, MenubarSubContent,
DropdownMenuSub, MenubarSubTrigger,
DropdownMenuSubContent, } from '@/components/ui/menubar'
DropdownMenuSubTrigger,
} from '@/components/ui/dropdown-menu'
const props = defineProps({ const props = defineProps({
title: { title: {
@ -42,14 +40,13 @@ function setStyle(title, value) {
</script> </script>
<template> <template>
<DropdownMenuSub> <MenubarSub>
<DropdownMenuSubTrigger> <MenubarSubTrigger>
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
<span>{{ props.title }}</span> <span>{{ props.title }}</span>
</DropdownMenuSubTrigger> </MenubarSubTrigger>
<DropdownMenuPortal> <MenubarSubContent class="max-h-56 overflow-auto">
<DropdownMenuSubContent class="max-h-56 overflow-auto"> <MenubarItem
<DropdownMenuItem
v-for="{ label, value, desc } in options" v-for="{ label, value, desc } in options"
:key="value" :key="value"
:label="label" :label="label"
@ -64,8 +61,7 @@ function setStyle(title, value) {
<DropdownMenuShortcut :style="setStyle(title, value)"> <DropdownMenuShortcut :style="setStyle(title, value)">
{{ desc }} {{ desc }}
</DropdownMenuShortcut> </DropdownMenuShortcut>
</DropdownMenuItem> </MenubarItem>
</DropdownMenuSubContent> </MenubarSubContent>
</DropdownMenuPortal> </MenubarSub>
</DropdownMenuSub>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { nextTick, reactive, ref } from 'vue' import { nextTick } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus'
import { Moon, Paintbrush, Sun } from 'lucide-vue-next' import { Moon, Paintbrush, Sun } from 'lucide-vue-next'
@ -9,7 +9,18 @@ import FileDropdown from './FileDropdown.vue'
import HelpDropdown from './HelpDropdown.vue' import HelpDropdown from './HelpDropdown.vue'
import StyleDropdown from './StyleDropdown.vue' import StyleDropdown from './StyleDropdown.vue'
import EditDropdown from './EditDropdown.vue' import EditDropdown from './EditDropdown.vue'
import { altSign, codeBlockThemeOptions, colorOptions, ctrlKey, ctrlSign, fontFamilyOptions, fontSizeOptions, legendOptions, shiftSign, themeOptions } from '@/config' import {
altSign,
codeBlockThemeOptions,
colorOptions,
ctrlKey,
ctrlSign,
fontFamilyOptions,
fontSizeOptions,
legendOptions,
shiftSign,
themeOptions,
} from '@/config'
import { import {
Select, Select,
@ -19,29 +30,22 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select'
import { import {
DropdownMenu, Menubar,
DropdownMenuContent, MenubarContent,
DropdownMenuItem, MenubarItem,
DropdownMenuSeparator, MenubarMenu,
DropdownMenuShortcut, MenubarSeparator,
DropdownMenuTrigger, MenubarShortcut,
} from '@/components/ui/dropdown-menu' MenubarTrigger,
import { } from '@/components/ui/menubar'
Popover,
PopoverContent, import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
PopoverTrigger,
} from '@/components/ui/popover'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { mergeCss, solveWeChatImage } from '@/utils' import { mergeCss, solveWeChatImage } from '@/utils'
import { useStore } from '@/stores' import { useStore } from '@/stores'
const emit = defineEmits([ const emit = defineEmits([`addFormat`, `formatContent`, `startCopy`, `endCopy`])
`addFormat`,
`formatContent`,
`startCopy`,
`endCopy`,
])
const formatItems = [ const formatItems = [
{ {
@ -78,18 +82,9 @@ const formatItems = [
const store = useStore() const store = useStore()
const { const { isDark, isCiteStatus, output, primaryColor } = storeToRefs(store)
isDark,
isCiteStatus,
output,
primaryColor,
} = storeToRefs(store)
const { const { toggleDark, editorRefresh, citeStatusChanged } = store
toggleDark,
editorRefresh,
citeStatusChanged,
} = store
// //
function copy() { function copy() {
@ -103,10 +98,7 @@ function copy() {
const originalItems = tempDiv.querySelectorAll(`li > ul, li > ol`) const originalItems = tempDiv.querySelectorAll(`li > ul, li > ol`)
originalItems.forEach((originalItem) => { originalItems.forEach((originalItem) => {
originalItem.parentElement.insertAdjacentElement( originalItem.parentElement.insertAdjacentElement(`afterend`, originalItem)
`afterend`,
originalItem,
)
}) })
// HTML // HTML
@ -161,76 +153,43 @@ function copy() {
}) })
}, 350) }, 350)
} }
const isClickTrigger = ref(false)
const isOpenList = reactive(Array.from({ length: 5 }).fill(false))
function clickTrigger() {
isClickTrigger.value = !isClickTrigger.value
}
function openDropdown(index) {
return () => {
isOpenList.fill(false)
isOpenList[index] = true
}
}
function updateOpen(isOpen) {
if (!isOpen) {
isClickTrigger.value = false
}
}
</script> </script>
<template> <template>
<div class="header-container"> <header class="header-container h-15 flex items-center px-5">
<div class="dropdowns flex flex-auto"> <Menubar class="menubar mr-auto">
<FileDropdown <FileDropdown />
:is-open="isClickTrigger && isOpenList[0]" :click-trigger="clickTrigger"
:open-dropdown="openDropdown(0)" :update-open="updateOpen"
/>
<DropdownMenu :open="isClickTrigger && isOpenList[1]" @update:open="updateOpen"> <MenubarMenu>
<DropdownMenuTrigger <MenubarTrigger> 格式 </MenubarTrigger>
class="flex items-center p-2 px-4 hover:bg-gray-2 dark:hover:bg-stone-9" :class="{ <MenubarContent class="w-60" align="start">
'bg-gray-2': isClickTrigger && isOpenList[1], <MenubarItem
'dark:bg-stone-9': isClickTrigger && isOpenList[1], v-for="{ label, kbd, emitArgs } in formatItems"
}" @click="clickTrigger()" @mouseenter="openDropdown(1)()" :key="kbd"
@click="$emit(...emitArgs)"
> >
格式
</DropdownMenuTrigger>
<DropdownMenuContent class="w-60" align="start">
<DropdownMenuItem v-for="{ label, kbd, emitArgs } in formatItems" :key="kbd" @click="$emit(...emitArgs);">
<el-icon class="mr-2 h-4 w-4" /> <el-icon class="mr-2 h-4 w-4" />
{{ label }} {{ label }}
<DropdownMenuShortcut> <MenubarShortcut>
<kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9"> <kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9">
{{ item }} {{ item }}
</kbd> </kbd>
</DropdownMenuShortcut> </MenubarShortcut>
</DropdownMenuItem> </MenubarItem>
<DropdownMenuSeparator /> <MenubarSeparator />
<DropdownMenuItem @click="citeStatusChanged()"> <MenubarItem @click="citeStatusChanged()">
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isCiteStatus }"> <el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isCiteStatus }">
<ElIconCheck /> <ElIconCheck />
</el-icon> </el-icon>
微信外链转底部引用 微信外链转底部引用
</DropdownMenuItem> </MenubarItem>
</DropdownMenuContent> </MenubarContent>
</DropdownMenu> </MenubarMenu>
<EditDropdown <EditDropdown />
:is-open="isClickTrigger && isOpenList[2]" :click-trigger="clickTrigger" <StyleDropdown />
:open-dropdown="openDropdown(2)" :update-open="updateOpen" <HelpDropdown />
/> </Menubar>
<StyleDropdown
:is-open="isClickTrigger && isOpenList[3]" :click-trigger="clickTrigger"
:open-dropdown="openDropdown(3)" :update-open="updateOpen"
/>
<HelpDropdown
:is-open="isClickTrigger && isOpenList[4]" :click-trigger="clickTrigger"
:open-dropdown="openDropdown(4)" :update-open="updateOpen"
/>
</div>
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
<Button variant="outline"> <Button variant="outline">
@ -240,25 +199,24 @@ function updateOpen(isOpen) {
<PopoverContent class="h-100 w-100 overflow-auto px-6" align="end"> <PopoverContent class="h-100 w-100 overflow-auto px-6" align="end">
<div class="space-y-4"> <div class="space-y-4">
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>主题</h2>
主题
</h2>
<div class="grid grid-cols-3 justify-items-center gap-2"> <div class="grid grid-cols-3 justify-items-center gap-2">
<Button <Button
v-for="{ label, value } in themeOptions" v-for="{ label, value } in themeOptions"
:key="value" :key="value"
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.theme === value }" @click="store.themeChanged(value)" :class="{
'border-black dark:border-white': store.theme === value,
}"
@click="store.themeChanged(value)"
> >
{{ label }} {{ label }}
</Button> </Button>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>字体</h2>
字体
</h2>
<div class="grid grid-cols-3 justify-items-center gap-2"> <div class="grid grid-cols-3 justify-items-center gap-2">
<Button <Button
v-for="{ label, value } in fontFamilyOptions" v-for="{ label, value } in fontFamilyOptions"
@ -273,9 +231,7 @@ function updateOpen(isOpen) {
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>字号</h2>
字号
</h2>
<div class="grid grid-cols-5 justify-items-center gap-2"> <div class="grid grid-cols-5 justify-items-center gap-2">
<Button <Button
v-for="{ value, desc } in fontSizeOptions" v-for="{ value, desc } in fontSizeOptions"
@ -283,26 +239,30 @@ function updateOpen(isOpen) {
variant="outline" variant="outline"
class="w-full" class="w-full"
:class="{ :class="{
'border-black dark:border-white': store.fontSize === value }" @click="store.sizeChanged(value)" 'border-black dark:border-white': store.fontSize === value,
}"
@click="store.sizeChanged(value)"
> >
{{ desc }} {{ desc }}
</Button> </Button>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>主题色</h2>
主题色
</h2>
<div class="grid grid-cols-3 justify-items-center gap-2"> <div class="grid grid-cols-3 justify-items-center gap-2">
<Button <Button
v-for="{ label, value } in colorOptions" v-for="{ label, value } in colorOptions"
:key="value" :key="value"
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.primaryColor === value }" @click="store.colorChanged(value)" :class="{
'border-black dark:border-white': store.primaryColor === value,
}"
@click="store.colorChanged(value)"
> >
<span <span
class="mr-2 inline-block h-4 w-4 rounded-full" :style="{ class="mr-2 inline-block h-4 w-4 rounded-full"
:style="{
background: value, background: value,
}" }"
/> />
@ -311,9 +271,7 @@ function updateOpen(isOpen) {
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>自定义主题色</h2>
自定义主题色
</h2>
<div> <div>
<el-color-picker <el-color-picker
v-model="primaryColor" v-model="primaryColor"
@ -324,16 +282,21 @@ function updateOpen(isOpen) {
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>代码块主题</h2>
代码块主题
</h2>
<div> <div>
<Select v-model="store.codeBlockTheme" @update:model-value="store.codeBlockThemeChanged"> <Select
v-model="store.codeBlockTheme"
@update:model-value="store.codeBlockThemeChanged"
>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a fruit" /> <SelectValue placeholder="Select a fruit" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem v-for="{ label, value } in codeBlockThemeOptions" :key="label" :value="value"> <SelectItem
v-for="{ label, value } in codeBlockThemeOptions"
:key="label"
:value="value"
>
{{ label }} {{ label }}
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
@ -341,16 +304,17 @@ function updateOpen(isOpen) {
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>图注格式</h2>
图注格式
</h2>
<div class="grid grid-cols-3 justify-items-center gap-2"> <div class="grid grid-cols-3 justify-items-center gap-2">
<Button <Button
v-for="{ label, value } in legendOptions" v-for="{ label, value } in legendOptions"
:key="value" :key="value"
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.legend === value }" @click="store.legendChanged(value)" :class="{
'border-black dark:border-white': store.legend === value,
}"
@click="store.legendChanged(value)"
> >
{{ label }} {{ label }}
</Button> </Button>
@ -358,84 +322,100 @@ function updateOpen(isOpen) {
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>Mac 代码块</h2>
Mac 代码块
</h2>
<div class="grid grid-cols-5 justify-items-center gap-2"> <div class="grid grid-cols-5 justify-items-center gap-2">
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.isMacCodeBlock }" @click="!store.isMacCodeBlock && store.macCodeBlockChanged()" :class="{
'border-black dark:border-white': store.isMacCodeBlock,
}"
@click="!store.isMacCodeBlock && store.macCodeBlockChanged()"
> >
开启 开启
</Button> </Button>
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': !store.isMacCodeBlock }" @click="store.isMacCodeBlock && store.macCodeBlockChanged()" :class="{
'border-black dark:border-white': !store.isMacCodeBlock,
}"
@click="store.isMacCodeBlock && store.macCodeBlockChanged()"
> >
关闭 关闭
</Button> </Button>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>微信外链转底部引用</h2>
微信外链转底部引用
</h2>
<div class="grid grid-cols-5 justify-items-center gap-2"> <div class="grid grid-cols-5 justify-items-center gap-2">
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.isCiteStatus }" @click="!store.isCiteStatus && store.citeStatusChanged()" :class="{
'border-black dark:border-white': store.isCiteStatus,
}"
@click="!store.isCiteStatus && store.citeStatusChanged()"
> >
开启 开启
</Button> </Button>
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': !store.isCiteStatus }" @click="store.isCiteStatus && store.citeStatusChanged()" :class="{
'border-black dark:border-white': !store.isCiteStatus,
}"
@click="store.isCiteStatus && store.citeStatusChanged()"
> >
关闭 关闭
</Button> </Button>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>编辑区位置</h2>
编辑区位置
</h2>
<div class="grid grid-cols-5 justify-items-center gap-2"> <div class="grid grid-cols-5 justify-items-center gap-2">
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': store.isEditOnLeft }" @click="!store.isEditOnLeft && store.toggleEditOnLeft()" :class="{
'border-black dark:border-white': store.isEditOnLeft,
}"
@click="!store.isEditOnLeft && store.toggleEditOnLeft()"
> >
左侧 左侧
</Button> </Button>
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': !store.isEditOnLeft }" @click="store.isEditOnLeft && store.toggleEditOnLeft()" :class="{
'border-black dark:border-white': !store.isEditOnLeft,
}"
@click="store.isEditOnLeft && store.toggleEditOnLeft()"
> >
右侧 右侧
</Button> </Button>
</div> </div>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h2> <h2>模式</h2>
模式
</h2>
<div class="grid grid-cols-5 justify-items-center gap-2"> <div class="grid grid-cols-5 justify-items-center gap-2">
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': !isDark }" @click="store.toggleDark(false)" :class="{
'border-black dark:border-white': !isDark,
}"
@click="store.toggleDark(false)"
> >
<Sun class="h-4 w-4" /> <Sun class="h-4 w-4" />
</Button> </Button>
<Button <Button
class="w-full" class="w-full"
variant="outline" :class="{ variant="outline"
'border-black dark:border-white': isDark }" @click="store.toggleDark(true)" :class="{
'border-black dark:border-white': isDark,
}"
@click="store.toggleDark(true)"
> >
<Moon class="h-4 w-4" /> <Moon class="h-4 w-4" />
</Button> </Button>
@ -449,18 +429,11 @@ function updateOpen(isOpen) {
</Button> </Button>
<PostInfo /> <PostInfo />
</div> </header>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.header-container { .menubar {
display: flex;
align-items: center;
height: 100%;
padding: 0 20px;
}
.dropdowns {
user-select: none; user-select: none;
} }

View File

@ -1,5 +1,13 @@
<script setup> <script setup>
import { ref, toRaw } from 'vue' import { ref, toRaw } from 'vue'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useStore } from '@/stores' import { useStore } from '@/stores'
import { createTable } from '@/utils' import { createTable } from '@/utils'
@ -28,15 +36,20 @@ function insertTable() {
resetVal() resetVal()
toggleShowInsertFormDialog() toggleShowInsertFormDialog()
} }
function onUpdate(val) {
if (!val) {
toggleShowInsertFormDialog(false)
}
}
</script> </script>
<template> <template>
<el-dialog <Dialog :open="store.isShowInsertFormDialog" @update:open="onUpdate">
title="插入表格" <DialogContent>
class="insert__dialog" <DialogHeader>
:model-value="store.isShowInsertFormDialog" <DialogTitle>插入表格</DialogTitle>
@close="toggleShowInsertFormDialog(false)" </DialogHeader>
>
<el-row class="tb-options" type="flex" align="middle" :gutter="10"> <el-row class="tb-options" type="flex" align="middle" :gutter="10">
<el-col :span="12"> <el-col :span="12">
行数 行数
@ -60,11 +73,7 @@ function insertTable() {
</el-col> </el-col>
</el-row> </el-row>
<table style="border-collapse: collapse" class="input-table"> <table style="border-collapse: collapse" class="input-table">
<tr <tr v-for="row in rowNum + 1" :key="row" :class="{ 'head-style': row === 1 }">
v-for="row in rowNum + 1"
:key="row"
:class="{ 'head-style': row === 1 }"
>
<td v-for="col in colNum" :key="col"> <td v-for="col in colNum" :key="col">
<el-input <el-input
v-model="tableData[`k_${row - 1}_${col - 1}`]" v-model="tableData[`k_${row - 1}_${col - 1}`]"
@ -74,26 +83,20 @@ function insertTable() {
</td> </td>
</tr> </tr>
</table> </table>
<template #footer>
<div class="dialog-footer"> <DialogFooter>
<el-button plain @click="toggleShowInsertFormDialog(false)"> <Button variant="outline" @click="toggleShowInsertFormDialog(false)">
</el-button> </Button>
<el-button type="primary" plain @click="insertTable"> <Button @click="insertTable">
</el-button> </Button>
</div> </DialogFooter>
</template> </DialogContent>
</el-dialog> </Dialog>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
:deep(.el-dialog) {
width: 55%;
min-height: 375px;
min-width: 440px;
}
.tb-options { .tb-options {
margin-bottom: 20px; margin-bottom: 20px;
} }

View File

@ -4,6 +4,8 @@ import CodeMirror from 'codemirror/lib/codemirror'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue' import { UploadFilled } from '@element-plus/icons-vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { checkImage, removeLeft } from '@/utils' import { checkImage, removeLeft } from '@/utils'
import { useStore } from '@/stores' import { useStore } from '@/stores'
@ -117,11 +119,12 @@ const imgHost = ref(`default`)
const formCustomElInput = ref(null) const formCustomElInput = ref(null)
const activeName = ref(`upload`) const activeName = ref(`upload`)
watch(activeName, async (val) => { watch(
activeName,
async (val) => {
if (val === `formCustom`) { if (val === `formCustom`) {
nextTick(() => { nextTick(() => {
const textarea const textarea = formCustomElInput.value.$el.querySelector(`textarea`)
= formCustomElInput.value.$el.querySelector(`textarea`)
formCustom.value.editor formCustom.value.editor
= formCustom.value.editor = formCustom.value.editor
|| CodeMirror.fromTextArea(textarea, { || CodeMirror.fromTextArea(textarea, {
@ -130,9 +133,11 @@ watch(activeName, async (val) => {
// formCustom.value.editor.setValue(formCustom.value.code) // formCustom.value.editor.setValue(formCustom.value.code)
}) })
} }
}, { },
{
immediate: true, immediate: true,
}) },
)
onBeforeMount(() => { onBeforeMount(() => {
if (localStorage.getItem(`githubConfig`)) { if (localStorage.getItem(`githubConfig`)) {
@ -278,15 +283,37 @@ function uploadImage(params) {
</script> </script>
<template> <template>
<el-dialog title="本地上传" class="upload__dialog" :model-value="store.isShowUploadImgDialog" @close="store.toggleShowUploadImgDialog(false)"> <Dialog v-model:open="store.isShowUploadImgDialog">
<DialogContent class="max-w-max">
<DialogHeader>
<DialogTitle>本地上传</DialogTitle>
</DialogHeader>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane class="upload-panel" label="选择上传" name="upload"> <el-tab-pane class="upload-panel" label="选择上传" name="upload">
<el-select v-model="imgHost" placeholder="请选择" size="small" @change="changeImgHost"> <el-select
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /> v-model="imgHost"
placeholder="请选择"
size="small"
@change="changeImgHost"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
<el-upload <el-upload
drag multiple action="" :headers="{ 'Content-Type': 'multipart/form-data' }" :show-file-list="false" drag
accept=".jpg, .jpeg, .png, .gif" name="file" :before-upload="beforeImageUpload" :http-request="uploadImage" multiple
action=""
:headers="{ 'Content-Type': 'multipart/form-data' }"
:show-file-list="false"
accept=".jpg, .jpeg, .png, .gif"
name="file"
:before-upload="beforeImageUpload"
:http-request="uploadImage"
> >
<el-icon class="el-icon--upload"> <el-icon class="el-icon--upload">
<UploadFilled /> <UploadFilled />
@ -337,16 +364,28 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> --> </el-tab-pane> -->
<el-tab-pane class="github-panel" label="GitHub 图床" name="github"> <el-tab-pane class="github-panel" label="GitHub 图床" name="github">
<el-form class="setting-form" :model="formGitHub" label-position="right" label-width="150px"> <el-form
class="setting-form"
:model="formGitHub"
label-position="right"
label-width="150px"
>
<el-form-item label="GitHub 仓库" :required="true"> <el-form-item label="GitHub 仓库" :required="true">
<el-input v-model.trim="formGitHub.repo" placeholder="如github.com/yanglbme/resource" /> <el-input
v-model.trim="formGitHub.repo"
placeholder="如github.com/yanglbme/resource"
/>
</el-form-item> </el-form-item>
<el-form-item label="分支"> <el-form-item label="分支">
<el-input v-model.trim="formGitHub.branch" placeholder="如release可不填默认 master" /> <el-input
v-model.trim="formGitHub.branch"
placeholder="如release可不填默认 master"
/>
</el-form-item> </el-form-item>
<el-form-item label="Token" :required="true"> <el-form-item label="Token" :required="true">
<el-input <el-input
v-model.trim="formGitHub.accessToken" show-password v-model.trim="formGitHub.accessToken"
show-password
placeholder="如cc1d0c1426d0fd0902bd2d7184b14da61b8abc46" placeholder="如cc1d0c1426d0fd0902bd2d7184b14da61b8abc46"
/> />
<el-link <el-link
@ -365,13 +404,22 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane class="github-panel" label="阿里云 OSS" name="aliOSS"> <el-tab-pane class="github-panel" label="阿里云 OSS" name="aliOSS">
<el-form class="setting-form" :model="formAliOSS" label-position="right" label-width="150px"> <el-form
class="setting-form"
:model="formAliOSS"
label-position="right"
label-width="150px"
>
<el-form-item label="AccessKey ID" :required="true"> <el-form-item label="AccessKey ID" :required="true">
<el-input v-model.trim="formAliOSS.accessKeyId" placeholder="如LTAI4GdoocsmdoxUf13ylbaNHk" /> <el-input
v-model.trim="formAliOSS.accessKeyId"
placeholder="如LTAI4GdoocsmdoxUf13ylbaNHk"
/>
</el-form-item> </el-form-item>
<el-form-item label="AccessKey Secret" :required="true"> <el-form-item label="AccessKey Secret" :required="true">
<el-input <el-input
v-model.trim="formAliOSS.accessKeySecret" show-password v-model.trim="formAliOSS.accessKeySecret"
show-password
placeholder="如cc1d0c142doocs0902bd2d7md4b14da6ylbabc46" placeholder="如cc1d0c142doocs0902bd2d7md4b14da6ylbabc46"
/> />
</el-form-item> </el-form-item>
@ -379,17 +427,34 @@ function uploadImage(params) {
<el-input v-model.trim="formAliOSS.bucket" placeholder="如doocs" /> <el-input v-model.trim="formAliOSS.bucket" placeholder="如doocs" />
</el-form-item> </el-form-item>
<el-form-item label="Bucket 所在区域" :required="true"> <el-form-item label="Bucket 所在区域" :required="true">
<el-input v-model.trim="formAliOSS.region" placeholder="如oss-cn-shenzhen" /> <el-input
v-model.trim="formAliOSS.region"
placeholder="如oss-cn-shenzhen"
/>
</el-form-item> </el-form-item>
<el-form-item label="UseSSL" :required="true"> <el-form-item label="UseSSL" :required="true">
<el-switch v-model="formAliOSS.useSSL" active-text="" inactive-text="" /> <el-switch
v-model="formAliOSS.useSSL"
active-text="是"
inactive-text="否"
/>
</el-form-item> </el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false"> <el-form-item label="自定义 CDN 域名" :required="false">
<el-input v-model.trim="formAliOSS.cdnHost" placeholder="如https://imagecdn.alidaodao.com可不填" /> <el-input
v-model.trim="formAliOSS.cdnHost"
placeholder="如https://imagecdn.alidaodao.com可不填"
/>
</el-form-item> </el-form-item>
<el-form-item label="存储路径"> <el-form-item label="存储路径">
<el-input v-model.trim="formAliOSS.path" placeholder="如img可不填默认为根目录" /> <el-input
<el-link type="primary" href="https://help.aliyun.com/document_detail/31883.html" target="_blank"> v-model.trim="formAliOSS.path"
placeholder="如img可不填默认为根目录"
/>
<el-link
type="primary"
href="https://help.aliyun.com/document_detail/31883.html"
target="_blank"
>
如何使用阿里云 OSS 如何使用阿里云 OSS
</el-link> </el-link>
</el-form-item> </el-form-item>
@ -401,28 +466,50 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane class="github-panel" label="腾讯云 COS" name="txCOS"> <el-tab-pane class="github-panel" label="腾讯云 COS" name="txCOS">
<el-form class="setting-form" :model="formTxCOS" label-position="right" label-width="150px"> <el-form
class="setting-form"
:model="formTxCOS"
label-position="right"
label-width="150px"
>
<el-form-item label="SecretId" :required="true"> <el-form-item label="SecretId" :required="true">
<el-input v-model.trim="formTxCOS.secretId" placeholder="如AKIDnQp1w3DOOCSs8F5MDp9tdoocsmdUPonW3" /> <el-input
v-model.trim="formTxCOS.secretId"
placeholder="如AKIDnQp1w3DOOCSs8F5MDp9tdoocsmdUPonW3"
/>
</el-form-item> </el-form-item>
<el-form-item label="SecretKey" :required="true"> <el-form-item label="SecretKey" :required="true">
<el-input <el-input
v-model.trim="formTxCOS.secretKey" show-password v-model.trim="formTxCOS.secretKey"
show-password
placeholder="如ukLmdtEJ9271f3DOocsMDsCXdS3YlbW0" placeholder="如ukLmdtEJ9271f3DOocsMDsCXdS3YlbW0"
/> />
</el-form-item> </el-form-item>
<el-form-item label="Bucket" :required="true"> <el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formTxCOS.bucket" placeholder="如doocs-3212520134" /> <el-input
v-model.trim="formTxCOS.bucket"
placeholder="如doocs-3212520134"
/>
</el-form-item> </el-form-item>
<el-form-item label="Bucket 所在区域" :required="true"> <el-form-item label="Bucket 所在区域" :required="true">
<el-input v-model.trim="formTxCOS.region" placeholder="如ap-guangzhou" /> <el-input v-model.trim="formTxCOS.region" placeholder="如ap-guangzhou" />
</el-form-item> </el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false"> <el-form-item label="自定义 CDN 域名" :required="false">
<el-input v-model.trim="formTxCOS.cdnHost" placeholder="如https://imagecdn.alidaodao.com可不填" /> <el-input
v-model.trim="formTxCOS.cdnHost"
placeholder="如https://imagecdn.alidaodao.com可不填"
/>
</el-form-item> </el-form-item>
<el-form-item label="存储路径"> <el-form-item label="存储路径">
<el-input v-model.trim="formTxCOS.path" placeholder="如img可不填默认根目录" /> <el-input
<el-link type="primary" href="https://cloud.tencent.com/document/product/436/38484" target="_blank"> v-model.trim="formTxCOS.path"
placeholder="如img可不填默认根目录"
/>
<el-link
type="primary"
href="https://cloud.tencent.com/document/product/436/38484"
target="_blank"
>
如何使用腾讯云 COS 如何使用腾讯云 COS
</el-link> </el-link>
</el-form-item> </el-form-item>
@ -434,13 +521,22 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane class="github-panel" label="七牛云 Kodo" name="qiniu"> <el-tab-pane class="github-panel" label="七牛云 Kodo" name="qiniu">
<el-form class="setting-form" :model="formQiniu" label-position="right" label-width="150px"> <el-form
class="setting-form"
:model="formQiniu"
label-position="right"
label-width="150px"
>
<el-form-item label="AccessKey" :required="true"> <el-form-item label="AccessKey" :required="true">
<el-input v-model.trim="formQiniu.accessKey" placeholder="如6DD3VaLJ_SQgOdoocsyTV_YWaDmdnL2n8EGx7kG" /> <el-input
v-model.trim="formQiniu.accessKey"
placeholder="如6DD3VaLJ_SQgOdoocsyTV_YWaDmdnL2n8EGx7kG"
/>
</el-form-item> </el-form-item>
<el-form-item label="SecretKey" :required="true"> <el-form-item label="SecretKey" :required="true">
<el-input <el-input
v-model.trim="formQiniu.secretKey" show-password v-model.trim="formQiniu.secretKey"
show-password
placeholder="如qgZa5qrvDOOcsmdKStD1oCjZ9nB7MDvJUs_34SIm" placeholder="如qgZa5qrvDOOcsmdKStD1oCjZ9nB7MDvJUs_34SIm"
/> />
</el-form-item> </el-form-item>
@ -448,14 +544,24 @@ function uploadImage(params) {
<el-input v-model.trim="formQiniu.bucket" placeholder="如md" /> <el-input v-model.trim="formQiniu.bucket" placeholder="如md" />
</el-form-item> </el-form-item>
<el-form-item label="Bucket 对应域名" :required="true"> <el-form-item label="Bucket 对应域名" :required="true">
<el-input v-model.trim="formQiniu.domain" placeholder="如https://images.123ylb.cn" /> <el-input
v-model.trim="formQiniu.domain"
placeholder="如https://images.123ylb.cn"
/>
</el-form-item> </el-form-item>
<el-form-item label="存储区域" :required="false"> <el-form-item label="存储区域" :required="false">
<el-input v-model.trim="formQiniu.region" placeholder="如z2可不填" /> <el-input v-model.trim="formQiniu.region" placeholder="如z2可不填" />
</el-form-item> </el-form-item>
<el-form-item label="存储路径" :required="false"> <el-form-item label="存储路径" :required="false">
<el-input v-model.trim="formQiniu.path" placeholder="如img可不填默认为根目录" /> <el-input
<el-link type="primary" href="https://developer.qiniu.com/kodo" target="_blank"> v-model.trim="formQiniu.path"
placeholder="如img可不填默认为根目录"
/>
<el-link
type="primary"
href="https://developer.qiniu.com/kodo"
target="_blank"
>
如何使用七牛云 Kodo 如何使用七牛云 Kodo
</el-link> </el-link>
</el-form-item> </el-form-item>
@ -467,12 +573,21 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane class="github-panel" label="MinIO" name="minio"> <el-tab-pane class="github-panel" label="MinIO" name="minio">
<el-form class="setting-form" :model="minioOSS" label-position="right" label-width="150px"> <el-form
class="setting-form"
:model="minioOSS"
label-position="right"
label-width="150px"
>
<el-form-item label="Endpoint" :required="true"> <el-form-item label="Endpoint" :required="true">
<el-input v-model.trim="minioOSS.endpoint" placeholder="如play.min.io" /> <el-input v-model.trim="minioOSS.endpoint" placeholder="如play.min.io" />
</el-form-item> </el-form-item>
<el-form-item label="Port" :required="false"> <el-form-item label="Port" :required="false">
<el-input v-model.trim="minioOSS.port" type="number" placeholder="如9000可不填http 默认为 80https 默认为 443" /> <el-input
v-model.trim="minioOSS.port"
type="number"
placeholder="如9000可不填http 默认为 80https 默认为 443"
/>
</el-form-item> </el-form-item>
<el-form-item label="UseSSL" :required="true"> <el-form-item label="UseSSL" :required="true">
<el-switch v-model="minioOSS.useSSL" active-text="" inactive-text="" /> <el-switch v-model="minioOSS.useSSL" active-text="" inactive-text="" />
@ -486,7 +601,8 @@ function uploadImage(params) {
<el-form-item label="SecretKey" :required="true"> <el-form-item label="SecretKey" :required="true">
<el-input v-model.trim="minioOSS.secretKey" placeholder="如asdasdasd" /> <el-input v-model.trim="minioOSS.secretKey" placeholder="如asdasdasd" />
<el-link <el-link
type="primary" href="http://docs.minio.org.cn/docs/master/minio-client-complete-guide" type="primary"
href="http://docs.minio.org.cn/docs/master/minio-client-complete-guide"
target="_blank" target="_blank"
> >
如何使用 MinIO 如何使用 MinIO
@ -503,10 +619,18 @@ function uploadImage(params) {
<el-form class="setting-form" :model="formCustom" label-position="right"> <el-form class="setting-form" :model="formCustom" label-position="right">
<el-form-item label="" :required="true"> <el-form-item label="" :required="true">
<el-input <el-input
ref="formCustomElInput" v-model="formCustom.code" class="formCustomElInput" type="textarea" ref="formCustomElInput"
resize="none" placeholder="Your custom code here." v-model="formCustom.code"
class="formCustomElInput"
type="textarea"
resize="none"
placeholder="Your custom code here."
/> />
<el-link type="primary" href="https://github.com/doocs/md#自定义上传逻辑" target="_blank"> <el-link
type="primary"
href="https://github.com/doocs/md#自定义上传逻辑"
target="_blank"
>
参数详情 参数详情
</el-link> </el-link>
</el-form-item> </el-form-item>
@ -518,21 +642,11 @@ function uploadImage(params) {
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-dialog> </DialogContent>
</Dialog>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.upload__dialog {
display: flex;
}
:deep(.el-dialog) {
width: 55%;
min-width: 640px;
min-height: 615px;
margin: auto !important;
}
:deep(.el-upload-dragger) { :deep(.el-upload-dragger) {
display: flex; display: flex;
flex-flow: column; flex-flow: column;

View File

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

View File

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

View File

@ -0,0 +1,50 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
DialogClose,
DialogContent,
type DialogContentEmits,
type DialogContentProps,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'radix-vue'
import { X } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<DialogContentProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<DialogContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DialogPortal>
<DialogOverlay
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
/>
<DialogContent
v-bind="forwarded"
:class="
cn(
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
props.class,
)"
>
<slot />
<DialogClose
class="data-[state=open]:bg-accent ring-offset-background data-[state=open]:text-muted-foreground focus:ring-ring absolute right-4 top-4 rounded-sm opacity-70 transition-opacity disabled:pointer-events-none hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { DialogDescription, type DialogDescriptionProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes[`class`] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<DialogDescription
v-bind="forwardedProps"
:class="cn('text-sm text-muted-foreground', props.class)"
>
<slot />
</DialogDescription>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: HTMLAttributes[`class`] }>()
</script>
<template>
<div
:class="
cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes[`class`]
}>()
</script>
<template>
<div
:class="cn('flex flex-col gap-y-1.5 text-center sm:text-left', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
DialogClose,
DialogContent,
type DialogContentEmits,
type DialogContentProps,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'radix-vue'
import { X } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<DialogContentProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<DialogContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DialogPortal>
<DialogOverlay
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80"
>
<DialogContent
:class="
cn(
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
props.class,
)
"
v-bind="forwarded"
@pointer-down-outside="(event) => {
const originalEvent = event.detail.originalEvent;
const target = originalEvent.target as HTMLElement;
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
event.preventDefault();
}
}"
>
<slot />
<DialogClose
class="hover:bg-secondary absolute right-3 top-3 rounded-md p-0.5 transition-colors"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogOverlay>
</DialogPortal>
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { DialogTitle, type DialogTitleProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes[`class`] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<DialogTitle
v-bind="forwardedProps"
:class="
cn(
'text-lg font-semibold leading-none tracking-tight',
props.class,
)
"
>
<slot />
</DialogTitle>
</template>

View File

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

View File

@ -0,0 +1,9 @@
export { default as Dialog } from './Dialog.vue'
export { default as DialogClose } from './DialogClose.vue'
export { default as DialogTrigger } from './DialogTrigger.vue'
export { default as DialogHeader } from './DialogHeader.vue'
export { default as DialogTitle } from './DialogTitle.vue'
export { default as DialogDescription } from './DialogDescription.vue'
export { default as DialogContent } from './DialogContent.vue'
export { default as DialogScrollContent } from './DialogScrollContent.vue'
export { default as DialogFooter } from './DialogFooter.vue'

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarRoot,
type MenubarRootEmits,
type MenubarRootProps,
useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarRootProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<MenubarRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<MenubarRoot
v-bind="forwarded"
:class="
cn(
'flex h-10 items-center gap-x-1 rounded-md border bg-background p-1',
props.class,
)
"
>
<slot />
</MenubarRoot>
</template>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarCheckboxItem,
type MenubarCheckboxItemEmits,
type MenubarCheckboxItemProps,
MenubarItemIndicator,
useForwardPropsEmits,
} from 'radix-vue'
import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarCheckboxItemProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<MenubarCheckboxItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<MenubarCheckboxItem
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)"
>
<span class="absolute left-2 h-3.5 w-3.5 flex items-center justify-center">
<MenubarItemIndicator>
<Check class="h-4 w-4" />
</MenubarItemIndicator>
</span>
<slot />
</MenubarCheckboxItem>
</template>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarContent,
type MenubarContentProps,
MenubarPortal,
useForwardProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<MenubarContentProps & { class?: HTMLAttributes[`class`] }>(),
{
align: `start`,
alignOffset: -4,
sideOffset: 8,
},
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<MenubarPortal>
<MenubarContent
v-bind="forwardedProps"
:class="
cn(
'z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 />
</MenubarContent>
</MenubarPortal>
</template>

View File

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

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarItem,
type MenubarItemEmits,
type MenubarItemProps,
useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarItemProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
const emits = defineEmits<MenubarItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<MenubarItem
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
props.class,
)"
>
<slot />
</MenubarItem>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { MenubarLabel, type MenubarLabelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarLabelProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
</script>
<template>
<MenubarLabel :class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)">
<slot />
</MenubarLabel>
</template>

View File

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

View File

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

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarItemIndicator,
MenubarRadioItem,
type MenubarRadioItemEmits,
type MenubarRadioItemProps,
useForwardPropsEmits,
} from 'radix-vue'
import { Circle } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarRadioItemProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<MenubarRadioItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<MenubarRadioItem
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)"
>
<span class="absolute left-2 h-3.5 w-3.5 flex items-center justify-center">
<MenubarItemIndicator>
<Circle class="h-2 w-2 fill-current" />
</MenubarItemIndicator>
</span>
<slot />
</MenubarRadioItem>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { MenubarSeparator, type MenubarSeparatorProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarSeparatorProps & { class?: HTMLAttributes[`class`] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<MenubarSeparator :class=" cn('-mx-1 my-1 h-px bg-muted', props.class)" v-bind="forwardedProps" />
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes[`class`]
}>()
</script>
<template>
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', props.class)">
<slot />
</span>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { MenubarSub, type MenubarSubEmits, useForwardPropsEmits } from 'radix-vue'
interface MenubarSubRootProps {
defaultOpen?: boolean
open?: boolean
}
const props = defineProps<MenubarSubRootProps>()
const emits = defineEmits<MenubarSubEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<MenubarSub v-bind="forwarded">
<slot />
</MenubarSub>
</template>

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
MenubarPortal,
MenubarSubContent,
type MenubarSubContentEmits,
type MenubarSubContentProps,
useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarSubContentProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<MenubarSubContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<MenubarPortal>
<MenubarSubContent
v-bind="forwarded"
:class="
cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 />
</MenubarSubContent>
</MenubarPortal>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { MenubarSubTrigger, type MenubarSubTriggerProps, useForwardProps } from 'radix-vue'
import { ChevronRight } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarSubTriggerProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<MenubarSubTrigger
v-bind="forwardedProps"
:class="cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
props.class,
)"
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</MenubarSubTrigger>
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { MenubarTrigger, type MenubarTriggerProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<MenubarTriggerProps & { class?: HTMLAttributes[`class`] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<MenubarTrigger
v-bind="forwardedProps"
:class="
cn(
'flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none hover:bg-accent focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
props.class,
)
"
>
<slot />
</MenubarTrigger>
</template>

View File

@ -0,0 +1,15 @@
export { default as Menubar } from './Menubar.vue'
export { default as MenubarItem } from './MenubarItem.vue'
export { default as MenubarContent } from './MenubarContent.vue'
export { default as MenubarGroup } from './MenubarGroup.vue'
export { default as MenubarMenu } from './MenubarMenu.vue'
export { default as MenubarRadioGroup } from './MenubarRadioGroup.vue'
export { default as MenubarRadioItem } from './MenubarRadioItem.vue'
export { default as MenubarCheckboxItem } from './MenubarCheckboxItem.vue'
export { default as MenubarSeparator } from './MenubarSeparator.vue'
export { default as MenubarSub } from './MenubarSub.vue'
export { default as MenubarSubContent } from './MenubarSubContent.vue'
export { default as MenubarSubTrigger } from './MenubarSubTrigger.vue'
export { default as MenubarTrigger } from './MenubarTrigger.vue'
export { default as MenubarShortcut } from './MenubarShortcut.vue'
export { default as MenubarLabel } from './MenubarLabel.vue'

View File

@ -6,6 +6,7 @@ import App from './App.vue'
import 'virtual:uno.css' import 'virtual:uno.css'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/xq-light.css' import 'codemirror/theme/xq-light.css'
import 'codemirror/theme/darcula.css'
/* 每个页面公共css */ /* 每个页面公共css */
import '@/assets/index.css' import '@/assets/index.css'

View File

@ -1,4 +1,4 @@
import { computed, markRaw, onMounted, ref } from 'vue' import { computed, markRaw, onMounted, ref, toRaw, watch } from 'vue'
import { createPinia, defineStore } from 'pinia' import { createPinia, defineStore } from 'pinia'
import { marked } from 'marked' import { marked } from 'marked'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
@ -184,11 +184,11 @@ export const useStore = defineStore(`store`, () => {
onMounted(() => { onMounted(() => {
const cssEditorDom = document.querySelector(`#cssEditor`) const cssEditorDom = document.querySelector(`#cssEditor`)
cssEditorDom.value = getCurrentTab().content cssEditorDom.value = getCurrentTab().content
const theme = isDark.value ? `darcula` : `xq-light`
cssEditor.value = markRaw( cssEditor.value = markRaw(
CodeMirror.fromTextArea(cssEditorDom, { CodeMirror.fromTextArea(cssEditorDom, {
mode: `css`, mode: `css`,
theme: `xq-light`, theme,
lineNumbers: false, lineNumbers: false,
styleActiveLine: true, styleActiveLine: true,
lineWrapping: true, lineWrapping: true,
@ -219,6 +219,11 @@ export const useStore = defineStore(`store`, () => {
}) })
}) })
watch(isDark, () => {
const theme = isDark.value ? `darcula` : `xq-light`
toRaw(cssEditor.value)?.setOption?.(`theme`, theme)
})
// 重置样式 // 重置样式
const resetStyle = () => { const resetStyle = () => {
isCiteStatus.value = false isCiteStatus.value = false

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, toRaw } from 'vue' import { onMounted, ref, toRaw, watch } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
@ -31,7 +31,7 @@ import {
} from '@/utils' } from '@/utils'
const store = useStore() const store = useStore()
const { output, editor, editorContent, isShowCssEditor } = storeToRefs(store) const { isDark, output, editor, editorContent, isShowCssEditor } = storeToRefs(store)
const { const {
editorRefresh, editorRefresh,
@ -178,6 +178,12 @@ function uploadImage(file, cb) {
const changeTimer = ref(0) const changeTimer = ref(0)
//
watch(isDark, () => {
const theme = isDark.value ? `darcula` : `xq-light`
toRaw(editor.value)?.setOption?.(`theme`, theme)
})
// //
function initEditor() { function initEditor() {
const editorDom = document.querySelector(`#editor`) const editorDom = document.querySelector(`#editor`)
@ -187,7 +193,7 @@ function initEditor() {
} }
editor.value = CodeMirror.fromTextArea(editorDom, { editor.value = CodeMirror.fromTextArea(editorDom, {
mode: `text/x-markdown`, mode: `text/x-markdown`,
theme: `xq-light`, theme: isDark.value ? `darcula` : `xq-light`,
lineNumbers: false, lineNumbers: false,
lineWrapping: true, lineWrapping: true,
styleActiveLine: true, styleActiveLine: true,
@ -370,22 +376,19 @@ onMounted(() => {
</script> </script>
<template> <template>
<div ref="container" class="container"> <div ref="container" class="container flex flex-col">
<el-container>
<el-header class="editor__header">
<EditorHeader <EditorHeader
@add-format="addFormat" @add-format="addFormat"
@format-content="formatContent" @format-content="formatContent"
@start-copy="startCopy" @start-copy="startCopy"
@end-copy="endCopy" @end-copy="endCopy"
/> />
</el-header> <main class="container-main flex-1">
<el-main class="container-main"> <el-row class="container-main-section h-full border-1">
<el-row class="container-main-section">
<el-col <el-col
ref="codeMirrorWrapper" ref="codeMirrorWrapper"
:span="isShowCssEditor ? 8 : 12" :span="isShowCssEditor ? 8 : 12"
class="codeMirror-wrapper" class="codeMirror-wrapper border-r-1"
:class="{ :class="{
'order-1': !store.isEditOnLeft, 'order-1': !store.isEditOnLeft,
}" }"
@ -429,10 +432,10 @@ onMounted(() => {
id="preview" id="preview"
ref="preview" ref="preview"
:span="isShowCssEditor ? 8 : 12" :span="isShowCssEditor ? 8 : 12"
class="preview-wrapper" class="preview-wrapper p-5"
> >
<div id="output-wrapper" :class="{ output_night: !backLight }"> <div id="output-wrapper" :class="{ output_night: !backLight }">
<div class="preview"> <div class="preview border shadow-xl">
<section id="output" v-html="output" /> <section id="output" v-html="output" />
<div v-if="isCoping" class="loading-mask"> <div v-if="isCoping" class="loading-mask">
<div class="loading-mask-box"> <div class="loading-mask-box">
@ -445,13 +448,10 @@ onMounted(() => {
</el-col> </el-col>
<CssEditor /> <CssEditor />
</el-row> </el-row>
</el-main> </main>
</el-container>
<UploadImgDialog <UploadImgDialog
@before-upload="beforeUpload"
@upload-image="uploadImage" @upload-image="uploadImage"
@uploaded="uploaded"
/> />
<InsertFormDialog /> <InsertFormDialog />
@ -466,20 +466,17 @@ onMounted(() => {
<style lang="less" scoped> <style lang="less" scoped>
.container { .container {
height: 100%; height: 100vh;
min-width: 100%; min-width: 100%;
padding: 0; padding: 0;
} }
.container-main { .container-main {
overflow: hidden;
padding: 20px; padding: 20px;
padding-top: 0; padding-top: 0;
} }
.container-main-section {
height: 100%;
}
#output-wrapper { #output-wrapper {
position: relative; position: relative;
user-select: text; user-select: text;