mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
style: update dialog (#402)
This commit is contained in:
parent
442beb8053
commit
d3a7d08f9d
@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 默认为 80,https 默认为 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 默认为 80,https 默认为 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;
|
||||
|
14
src/components/ui/dialog/Dialog.vue
Normal file
14
src/components/ui/dialog/Dialog.vue
Normal 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>
|
11
src/components/ui/dialog/DialogClose.vue
Normal file
11
src/components/ui/dialog/DialogClose.vue
Normal 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>
|
50
src/components/ui/dialog/DialogContent.vue
Normal file
50
src/components/ui/dialog/DialogContent.vue
Normal 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>
|
24
src/components/ui/dialog/DialogDescription.vue
Normal file
24
src/components/ui/dialog/DialogDescription.vue
Normal 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>
|
19
src/components/ui/dialog/DialogFooter.vue
Normal file
19
src/components/ui/dialog/DialogFooter.vue
Normal 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>
|
16
src/components/ui/dialog/DialogHeader.vue
Normal file
16
src/components/ui/dialog/DialogHeader.vue
Normal 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>
|
59
src/components/ui/dialog/DialogScrollContent.vue
Normal file
59
src/components/ui/dialog/DialogScrollContent.vue
Normal 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>
|
29
src/components/ui/dialog/DialogTitle.vue
Normal file
29
src/components/ui/dialog/DialogTitle.vue
Normal 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>
|
11
src/components/ui/dialog/DialogTrigger.vue
Normal file
11
src/components/ui/dialog/DialogTrigger.vue
Normal 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>
|
9
src/components/ui/dialog/index.ts
Normal file
9
src/components/ui/dialog/index.ts
Normal 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'
|
35
src/components/ui/menubar/Menubar.vue
Normal file
35
src/components/ui/menubar/Menubar.vue
Normal 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>
|
40
src/components/ui/menubar/MenubarCheckboxItem.vue
Normal file
40
src/components/ui/menubar/MenubarCheckboxItem.vue
Normal 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>
|
43
src/components/ui/menubar/MenubarContent.vue
Normal file
43
src/components/ui/menubar/MenubarContent.vue
Normal 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>
|
11
src/components/ui/menubar/MenubarGroup.vue
Normal file
11
src/components/ui/menubar/MenubarGroup.vue
Normal 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>
|
35
src/components/ui/menubar/MenubarItem.vue
Normal file
35
src/components/ui/menubar/MenubarItem.vue
Normal 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>
|
13
src/components/ui/menubar/MenubarLabel.vue
Normal file
13
src/components/ui/menubar/MenubarLabel.vue
Normal 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>
|
11
src/components/ui/menubar/MenubarMenu.vue
Normal file
11
src/components/ui/menubar/MenubarMenu.vue
Normal 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>
|
20
src/components/ui/menubar/MenubarRadioGroup.vue
Normal file
20
src/components/ui/menubar/MenubarRadioGroup.vue
Normal 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>
|
40
src/components/ui/menubar/MenubarRadioItem.vue
Normal file
40
src/components/ui/menubar/MenubarRadioItem.vue
Normal 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>
|
19
src/components/ui/menubar/MenubarSeparator.vue
Normal file
19
src/components/ui/menubar/MenubarSeparator.vue
Normal 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>
|
14
src/components/ui/menubar/MenubarShortcut.vue
Normal file
14
src/components/ui/menubar/MenubarShortcut.vue
Normal 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>
|
19
src/components/ui/menubar/MenubarSub.vue
Normal file
19
src/components/ui/menubar/MenubarSub.vue
Normal 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>
|
39
src/components/ui/menubar/MenubarSubContent.vue
Normal file
39
src/components/ui/menubar/MenubarSubContent.vue
Normal 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>
|
30
src/components/ui/menubar/MenubarSubTrigger.vue
Normal file
30
src/components/ui/menubar/MenubarSubTrigger.vue
Normal 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>
|
29
src/components/ui/menubar/MenubarTrigger.vue
Normal file
29
src/components/ui/menubar/MenubarTrigger.vue
Normal 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>
|
15
src/components/ui/menubar/index.ts
Normal file
15
src/components/ui/menubar/index.ts
Normal 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'
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user