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 {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.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%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--card-foreground: 0 0% 3.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--destructive-foreground: 0 0% 98%;
--border:0 0% 89.8%;
--input:0 0% 89.8%;
--ring:0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--background:0 0% 3.9%;
--foreground:0 0% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--card:0 0% 3.9%;
--card-foreground:0 0% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--popover:0 0% 3.9%;
--popover-foreground:0 0% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--primary:0 0% 98%;
--primary-foreground:0 0% 9%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--secondary:0 0% 14.9%;
--secondary-foreground:0 0% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--muted:0 0% 14.9%;
--muted-foreground:0 0% 63.9%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent:0 0% 14.9%;
--accent-foreground:0 0% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive:0 62.8% 30.6%;
--destructive-foreground:0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
--border:0 0% 14.9%;
--input:0 0% 14.9%;
--ring:0 0% 83.1%;
}
}

View File

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

View File

@ -1,35 +1,13 @@
@nightBgColor: #333333;
@nightPreviewColor: #1e1e1e;
@nightHeaderColor: #3c3c3c;
@nightCodeMirrorColor: #1e1e1e;
@nightPreviewColor: #191919;
@nightCodeMirrorColor: #191919;
@nightActiveCodeMirrorColor: gray;
@nightFontColor: gray;
@nightLinkColor: #8e9eb9;
@nightLinkTextColor: #84868b;
@nightWhiteColor: #f0f0f0;
@nightButtonBg: #1e1e1e;
@nightButtonHoverColor: #84868b;
@nightLineColor: #84868b;
.dark {
.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 {
.preview {
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 {
background-color: @nightCodeMirrorColor;
}
@ -69,7 +33,6 @@
.CodeMirror {
padding-bottom: 0;
height: 100% !important;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
font-size: 14px;
font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue',
sans-serif !important;

View File

@ -67,7 +67,7 @@ function handleTabsEdit(targetName, action) {
<template>
<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
v-model="store.cssContentConfig.active"
type="border-card"

View File

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

View File

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

View File

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

View File

@ -3,41 +3,21 @@ import { ref } from '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)
</script>
<template>
<DropdownMenu :open="props.isOpen" @update:open="props.updateOpen">
<DropdownMenuTrigger
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()"
>
<MenubarMenu>
<MenubarTrigger>
帮助
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem @click="aboutDialogVisible = true">
</MenubarTrigger>
<MenubarContent align="start">
<MenubarItem @click="aboutDialogVisible = true">
<el-icon class="mr-2 h-4 w-4" />
<span>关于</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<AboutDialog
:visible="aboutDialogVisible"
@close="aboutDialogVisible = false"
/>
<AboutDialog :visible="aboutDialogVisible" @close="aboutDialogVisible = false" />
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import CodeMirror from 'codemirror/lib/codemirror'
import { ElMessage } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { checkImage, removeLeft } from '@/utils'
import { useStore } from '@/stores'
@ -117,22 +119,25 @@ const imgHost = ref(`default`)
const formCustomElInput = ref(null)
const activeName = ref(`upload`)
watch(activeName, async (val) => {
if (val === `formCustom`) {
nextTick(() => {
const textarea
= formCustomElInput.value.$el.querySelector(`textarea`)
formCustom.value.editor
= formCustom.value.editor
|| CodeMirror.fromTextArea(textarea, {
mode: `javascript`,
})
// formCustom.value.editor.setValue(formCustom.value.code)
})
}
}, {
immediate: true,
})
watch(
activeName,
async (val) => {
if (val === `formCustom`) {
nextTick(() => {
const textarea = formCustomElInput.value.$el.querySelector(`textarea`)
formCustom.value.editor
= formCustom.value.editor
|| CodeMirror.fromTextArea(textarea, {
mode: `javascript`,
})
// formCustom.value.editor.setValue(formCustom.value.code)
})
}
},
{
immediate: true,
},
)
onBeforeMount(() => {
if (localStorage.getItem(`githubConfig`)) {
@ -278,26 +283,48 @@ function uploadImage(params) {
</script>
<template>
<el-dialog title="本地上传" class="upload__dialog" :model-value="store.isShowUploadImgDialog" @close="store.toggleShowUploadImgDialog(false)">
<el-tabs v-model="activeName">
<el-tab-pane class="upload-panel" label="选择上传" name="upload">
<el-select 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-upload
drag 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">
<UploadFilled />
</el-icon>
<div class="el-upload__text">
将图片拖到此处
<em>点击上传</em>
</div>
</el-upload>
</el-tab-pane>
<!-- <el-tab-pane class="github-panel" label="Gitee 图床" name="gitee">
<Dialog v-model:open="store.isShowUploadImgDialog">
<DialogContent class="max-w-max">
<DialogHeader>
<DialogTitle>本地上传</DialogTitle>
</DialogHeader>
<el-tabs v-model="activeName">
<el-tab-pane class="upload-panel" label="选择上传" name="upload">
<el-select
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-upload
drag
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">
<UploadFilled />
</el-icon>
<div class="el-upload__text">
将图片拖到此处
<em>点击上传</em>
</div>
</el-upload>
</el-tab-pane>
<!-- <el-tab-pane class="github-panel" label="Gitee 图床" name="gitee">
<el-form
class="setting-form"
:model="formGitee"
@ -336,203 +363,290 @@ function uploadImage(params) {
</el-form-item>
</el-form>
</el-tab-pane> -->
<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-item label="GitHub 仓库" :required="true">
<el-input v-model.trim="formGitHub.repo" placeholder="如github.com/yanglbme/resource" />
</el-form-item>
<el-form-item label="分支">
<el-input v-model.trim="formGitHub.branch" placeholder="如release可不填默认 master" />
</el-form-item>
<el-form-item label="Token" :required="true">
<el-input
v-model.trim="formGitHub.accessToken" show-password
placeholder="如cc1d0c1426d0fd0902bd2d7184b14da61b8abc46"
/>
<el-link
type="primary"
href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token"
target="_blank"
>
如何获取 GitHub Token
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveGitHubConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="AccessKey ID" :required="true">
<el-input v-model.trim="formAliOSS.accessKeyId" placeholder="如LTAI4GdoocsmdoxUf13ylbaNHk" />
</el-form-item>
<el-form-item label="AccessKey Secret" :required="true">
<el-input
v-model.trim="formAliOSS.accessKeySecret" show-password
placeholder="如cc1d0c142doocs0902bd2d7md4b14da6ylbabc46"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formAliOSS.bucket" placeholder="如doocs" />
</el-form-item>
<el-form-item label="Bucket 所在区域" :required="true">
<el-input v-model.trim="formAliOSS.region" placeholder="如oss-cn-shenzhen" />
</el-form-item>
<el-form-item label="UseSSL" :required="true">
<el-switch v-model="formAliOSS.useSSL" active-text="" inactive-text="" />
</el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false">
<el-input v-model.trim="formAliOSS.cdnHost" placeholder="如https://imagecdn.alidaodao.com可不填" />
</el-form-item>
<el-form-item label="存储路径">
<el-input v-model.trim="formAliOSS.path" placeholder="如img可不填默认为根目录" />
<el-link type="primary" href="https://help.aliyun.com/document_detail/31883.html" target="_blank">
如何使用阿里云 OSS
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAliOSSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="SecretId" :required="true">
<el-input v-model.trim="formTxCOS.secretId" placeholder="如AKIDnQp1w3DOOCSs8F5MDp9tdoocsmdUPonW3" />
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input
v-model.trim="formTxCOS.secretKey" show-password
placeholder="如ukLmdtEJ9271f3DOocsMDsCXdS3YlbW0"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formTxCOS.bucket" placeholder="如doocs-3212520134" />
</el-form-item>
<el-form-item label="Bucket 所在区域" :required="true">
<el-input v-model.trim="formTxCOS.region" placeholder="如ap-guangzhou" />
</el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false">
<el-input v-model.trim="formTxCOS.cdnHost" placeholder="如https://imagecdn.alidaodao.com可不填" />
</el-form-item>
<el-form-item label="存储路径">
<el-input v-model.trim="formTxCOS.path" placeholder="如img可不填默认根目录" />
<el-link type="primary" href="https://cloud.tencent.com/document/product/436/38484" target="_blank">
如何使用腾讯云 COS
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveTxCOSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="AccessKey" :required="true">
<el-input v-model.trim="formQiniu.accessKey" placeholder="如6DD3VaLJ_SQgOdoocsyTV_YWaDmdnL2n8EGx7kG" />
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input
v-model.trim="formQiniu.secretKey" show-password
placeholder="如qgZa5qrvDOOcsmdKStD1oCjZ9nB7MDvJUs_34SIm"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formQiniu.bucket" placeholder="如md" />
</el-form-item>
<el-form-item label="Bucket 对应域名" :required="true">
<el-input v-model.trim="formQiniu.domain" placeholder="如https://images.123ylb.cn" />
</el-form-item>
<el-form-item label="存储区域" :required="false">
<el-input v-model.trim="formQiniu.region" placeholder="如z2可不填" />
</el-form-item>
<el-form-item label="存储路径" :required="false">
<el-input v-model.trim="formQiniu.path" placeholder="如img可不填默认为根目录" />
<el-link type="primary" href="https://developer.qiniu.com/kodo" target="_blank">
如何使用七牛云 Kodo
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveQiniuConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="Endpoint" :required="true">
<el-input v-model.trim="minioOSS.endpoint" placeholder="如play.min.io" />
</el-form-item>
<el-form-item label="Port" :required="false">
<el-input v-model.trim="minioOSS.port" type="number" placeholder="如9000可不填http 默认为 80https 默认为 443" />
</el-form-item>
<el-form-item label="UseSSL" :required="true">
<el-switch v-model="minioOSS.useSSL" active-text="" inactive-text="" />
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="minioOSS.bucket" placeholder="如doocs" />
</el-form-item>
<el-form-item label="AccessKey" :required="true">
<el-input v-model.trim="minioOSS.accessKey" placeholder="如zhangsan" />
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input v-model.trim="minioOSS.secretKey" placeholder="如asdasdasd" />
<el-link
type="primary" href="http://docs.minio.org.cn/docs/master/minio-client-complete-guide"
target="_blank"
>
如何使用 MinIO
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveMinioOSSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane class="github-panel formCustom" label="自定义代码" name="formCustom">
<el-form class="setting-form" :model="formCustom" label-position="right">
<el-form-item label="" :required="true">
<el-input
ref="formCustomElInput" 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>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="formCustomSave">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-dialog>
<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-item label="GitHub 仓库" :required="true">
<el-input
v-model.trim="formGitHub.repo"
placeholder="如github.com/yanglbme/resource"
/>
</el-form-item>
<el-form-item label="分支">
<el-input
v-model.trim="formGitHub.branch"
placeholder="如release可不填默认 master"
/>
</el-form-item>
<el-form-item label="Token" :required="true">
<el-input
v-model.trim="formGitHub.accessToken"
show-password
placeholder="如cc1d0c1426d0fd0902bd2d7184b14da61b8abc46"
/>
<el-link
type="primary"
href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token"
target="_blank"
>
如何获取 GitHub Token
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveGitHubConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="AccessKey ID" :required="true">
<el-input
v-model.trim="formAliOSS.accessKeyId"
placeholder="如LTAI4GdoocsmdoxUf13ylbaNHk"
/>
</el-form-item>
<el-form-item label="AccessKey Secret" :required="true">
<el-input
v-model.trim="formAliOSS.accessKeySecret"
show-password
placeholder="如cc1d0c142doocs0902bd2d7md4b14da6ylbabc46"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formAliOSS.bucket" placeholder="如doocs" />
</el-form-item>
<el-form-item label="Bucket 所在区域" :required="true">
<el-input
v-model.trim="formAliOSS.region"
placeholder="如oss-cn-shenzhen"
/>
</el-form-item>
<el-form-item label="UseSSL" :required="true">
<el-switch
v-model="formAliOSS.useSSL"
active-text="是"
inactive-text="否"
/>
</el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false">
<el-input
v-model.trim="formAliOSS.cdnHost"
placeholder="如https://imagecdn.alidaodao.com可不填"
/>
</el-form-item>
<el-form-item label="存储路径">
<el-input
v-model.trim="formAliOSS.path"
placeholder="如img可不填默认为根目录"
/>
<el-link
type="primary"
href="https://help.aliyun.com/document_detail/31883.html"
target="_blank"
>
如何使用阿里云 OSS
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAliOSSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="SecretId" :required="true">
<el-input
v-model.trim="formTxCOS.secretId"
placeholder="如AKIDnQp1w3DOOCSs8F5MDp9tdoocsmdUPonW3"
/>
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input
v-model.trim="formTxCOS.secretKey"
show-password
placeholder="如ukLmdtEJ9271f3DOocsMDsCXdS3YlbW0"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input
v-model.trim="formTxCOS.bucket"
placeholder="如doocs-3212520134"
/>
</el-form-item>
<el-form-item label="Bucket 所在区域" :required="true">
<el-input v-model.trim="formTxCOS.region" placeholder="如ap-guangzhou" />
</el-form-item>
<el-form-item label="自定义 CDN 域名" :required="false">
<el-input
v-model.trim="formTxCOS.cdnHost"
placeholder="如https://imagecdn.alidaodao.com可不填"
/>
</el-form-item>
<el-form-item label="存储路径">
<el-input
v-model.trim="formTxCOS.path"
placeholder="如img可不填默认根目录"
/>
<el-link
type="primary"
href="https://cloud.tencent.com/document/product/436/38484"
target="_blank"
>
如何使用腾讯云 COS
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveTxCOSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="AccessKey" :required="true">
<el-input
v-model.trim="formQiniu.accessKey"
placeholder="如6DD3VaLJ_SQgOdoocsyTV_YWaDmdnL2n8EGx7kG"
/>
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input
v-model.trim="formQiniu.secretKey"
show-password
placeholder="如qgZa5qrvDOOcsmdKStD1oCjZ9nB7MDvJUs_34SIm"
/>
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="formQiniu.bucket" placeholder="如md" />
</el-form-item>
<el-form-item label="Bucket 对应域名" :required="true">
<el-input
v-model.trim="formQiniu.domain"
placeholder="如https://images.123ylb.cn"
/>
</el-form-item>
<el-form-item label="存储区域" :required="false">
<el-input v-model.trim="formQiniu.region" placeholder="如z2可不填" />
</el-form-item>
<el-form-item label="存储路径" :required="false">
<el-input
v-model.trim="formQiniu.path"
placeholder="如img可不填默认为根目录"
/>
<el-link
type="primary"
href="https://developer.qiniu.com/kodo"
target="_blank"
>
如何使用七牛云 Kodo
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveQiniuConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<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-item label="Endpoint" :required="true">
<el-input v-model.trim="minioOSS.endpoint" placeholder="如play.min.io" />
</el-form-item>
<el-form-item label="Port" :required="false">
<el-input
v-model.trim="minioOSS.port"
type="number"
placeholder="如9000可不填http 默认为 80https 默认为 443"
/>
</el-form-item>
<el-form-item label="UseSSL" :required="true">
<el-switch v-model="minioOSS.useSSL" active-text="" inactive-text="" />
</el-form-item>
<el-form-item label="Bucket" :required="true">
<el-input v-model.trim="minioOSS.bucket" placeholder="如doocs" />
</el-form-item>
<el-form-item label="AccessKey" :required="true">
<el-input v-model.trim="minioOSS.accessKey" placeholder="如zhangsan" />
</el-form-item>
<el-form-item label="SecretKey" :required="true">
<el-input v-model.trim="minioOSS.secretKey" placeholder="如asdasdasd" />
<el-link
type="primary"
href="http://docs.minio.org.cn/docs/master/minio-client-complete-guide"
target="_blank"
>
如何使用 MinIO
</el-link>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveMinioOSSConfiguration">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane class="github-panel formCustom" label="自定义代码" name="formCustom">
<el-form class="setting-form" :model="formCustom" label-position="right">
<el-form-item label="" :required="true">
<el-input
ref="formCustomElInput"
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>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="formCustomSave">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</DialogContent>
</Dialog>
</template>
<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) {
display: flex;
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 'codemirror/lib/codemirror.css'
import 'codemirror/theme/xq-light.css'
import 'codemirror/theme/darcula.css'
/* 每个页面公共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 { marked } from 'marked'
import CodeMirror from 'codemirror'
@ -184,11 +184,11 @@ export const useStore = defineStore(`store`, () => {
onMounted(() => {
const cssEditorDom = document.querySelector(`#cssEditor`)
cssEditorDom.value = getCurrentTab().content
const theme = isDark.value ? `darcula` : `xq-light`
cssEditor.value = markRaw(
CodeMirror.fromTextArea(cssEditorDom, {
mode: `css`,
theme: `xq-light`,
theme,
lineNumbers: false,
styleActiveLine: 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 = () => {
isCiteStatus.value = false

View File

@ -1,5 +1,5 @@
<script setup>
import { onMounted, ref, toRaw } from 'vue'
import { onMounted, ref, toRaw, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus'
import CodeMirror from 'codemirror'
@ -31,7 +31,7 @@ import {
} from '@/utils'
const store = useStore()
const { output, editor, editorContent, isShowCssEditor } = storeToRefs(store)
const { isDark, output, editor, editorContent, isShowCssEditor } = storeToRefs(store)
const {
editorRefresh,
@ -178,6 +178,12 @@ function uploadImage(file, cb) {
const changeTimer = ref(0)
//
watch(isDark, () => {
const theme = isDark.value ? `darcula` : `xq-light`
toRaw(editor.value)?.setOption?.(`theme`, theme)
})
//
function initEditor() {
const editorDom = document.querySelector(`#editor`)
@ -187,7 +193,7 @@ function initEditor() {
}
editor.value = CodeMirror.fromTextArea(editorDom, {
mode: `text/x-markdown`,
theme: `xq-light`,
theme: isDark.value ? `darcula` : `xq-light`,
lineNumbers: false,
lineWrapping: true,
styleActiveLine: true,
@ -370,88 +376,82 @@ onMounted(() => {
</script>
<template>
<div ref="container" class="container">
<el-container>
<el-header class="editor__header">
<EditorHeader
@add-format="addFormat"
@format-content="formatContent"
@start-copy="startCopy"
@end-copy="endCopy"
/>
</el-header>
<el-main class="container-main">
<el-row class="container-main-section">
<el-col
ref="codeMirrorWrapper"
:span="isShowCssEditor ? 8 : 12"
class="codeMirror-wrapper"
:class="{
'order-1': !store.isEditOnLeft,
}"
>
<ContextMenu>
<ContextMenuTrigger>
<textarea
id="editor"
type="textarea"
placeholder="Your markdown text here."
/>
</ContextMenuTrigger>
<ContextMenuContent class="w-64">
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
上传图片
</ContextMenuItem>
<ContextMenuItem inset @click="toggleShowInsertFormDialog()">
插入表格
</ContextMenuItem>
<ContextMenuItem inset @click="resetStyleConfirm()">
恢复默认样式
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem inset @click="importMarkdownContent()">
导入 .md 文档
</ContextMenuItem>
<ContextMenuItem inset @click="exportEditorContent2MD()">
导出 .md 文档
</ContextMenuItem>
<ContextMenuItem inset @click="exportEditorContent2HTML()">
导出 .html
</ContextMenuItem>
<ContextMenuItem inset @click="formatContent()">
格式化
<ContextMenuShortcut>{{ altSign }} + {{ shiftSign }} + F</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</el-col>
<el-col
id="preview"
ref="preview"
:span="isShowCssEditor ? 8 : 12"
class="preview-wrapper"
>
<div id="output-wrapper" :class="{ output_night: !backLight }">
<div class="preview">
<section id="output" v-html="output" />
<div v-if="isCoping" class="loading-mask">
<div class="loading-mask-box">
<div class="loading__img" />
<span>正在生成</span>
</div>
<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">
<el-row class="container-main-section h-full border-1">
<el-col
ref="codeMirrorWrapper"
:span="isShowCssEditor ? 8 : 12"
class="codeMirror-wrapper border-r-1"
:class="{
'order-1': !store.isEditOnLeft,
}"
>
<ContextMenu>
<ContextMenuTrigger>
<textarea
id="editor"
type="textarea"
placeholder="Your markdown text here."
/>
</ContextMenuTrigger>
<ContextMenuContent class="w-64">
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
上传图片
</ContextMenuItem>
<ContextMenuItem inset @click="toggleShowInsertFormDialog()">
插入表格
</ContextMenuItem>
<ContextMenuItem inset @click="resetStyleConfirm()">
恢复默认样式
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem inset @click="importMarkdownContent()">
导入 .md 文档
</ContextMenuItem>
<ContextMenuItem inset @click="exportEditorContent2MD()">
导出 .md 文档
</ContextMenuItem>
<ContextMenuItem inset @click="exportEditorContent2HTML()">
导出 .html
</ContextMenuItem>
<ContextMenuItem inset @click="formatContent()">
格式化
<ContextMenuShortcut>{{ altSign }} + {{ shiftSign }} + F</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</el-col>
<el-col
id="preview"
ref="preview"
:span="isShowCssEditor ? 8 : 12"
class="preview-wrapper p-5"
>
<div id="output-wrapper" :class="{ output_night: !backLight }">
<div class="preview border shadow-xl">
<section id="output" v-html="output" />
<div v-if="isCoping" class="loading-mask">
<div class="loading-mask-box">
<div class="loading__img" />
<span>正在生成</span>
</div>
</div>
</div>
</el-col>
<CssEditor />
</el-row>
</el-main>
</el-container>
</div>
</el-col>
<CssEditor />
</el-row>
</main>
<UploadImgDialog
@before-upload="beforeUpload"
@upload-image="uploadImage"
@uploaded="uploaded"
/>
<InsertFormDialog />
@ -466,20 +466,17 @@ onMounted(() => {
<style lang="less" scoped>
.container {
height: 100%;
height: 100vh;
min-width: 100%;
padding: 0;
}
.container-main {
overflow: hidden;
padding: 20px;
padding-top: 0;
}
.container-main-section {
height: 100%;
}
#output-wrapper {
position: relative;
user-select: text;