mirror of
https://github.com/doocs/md.git
synced 2025-01-22 20:04:39 +08:00
chore: update layout (#493)
* chore: update layout * fix: package * style: adjust layout * style: enhance layout and border styling in CodemirrorEditor.vue * style: improve transition effects for PostSlider and RightSlider by a hack way * style: refine border and footer layout * style: update icon * style: set select-none * chore: remove overflow property * style: reset code block theme * chore: rename `文章` to `内容` --------- Co-authored-by: dribble-njr <wzw15292257101@163.com> Co-authored-by: yanglbme <szuyanglb@outlook.com>
This commit is contained in:
parent
de2cc52e15
commit
741322d69e
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
[![github](https://badgen.net/badge/>>/GitHub/cyan)](https://github.com/doocs/md/releases) [![gitee](https://badgen.net/badge/>>/Gitee/cyan)](https://gitee.com/doocs/md/releases)
|
[![github](https://badgen.net/badge/>>/GitHub/cyan)](https://github.com/doocs/md/releases) [![gitee](https://badgen.net/badge/>>/Gitee/cyan)](https://gitee.com/doocs/md/releases)
|
||||||
|
|
||||||
> Markdown 文档自动即时渲染为微信图文,让你不再为微信文章排版而发愁!
|
> Markdown 文档自动即时渲染为微信图文,让你不再为微信内容排版而发愁!
|
||||||
|
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
12
README.md
12
README.md
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
## 项目介绍
|
## 项目介绍
|
||||||
|
|
||||||
Markdown 文档自动即时渲染为微信图文,让你不再为微信文章排版而发愁!只要你会基本的 Markdown 语法,就能做出一篇样式简洁而又美观大方的微信图文。
|
Markdown 文档自动即时渲染为微信图文,让你不再为微信内容排版而发愁!只要你会基本的 Markdown 语法,就能做出一篇样式简洁而又美观大方的微信图文。
|
||||||
|
|
||||||
## 在线编辑器地址
|
## 在线编辑器地址
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章
|
|||||||
|
|
||||||
## 为何二次开发
|
## 为何二次开发
|
||||||
|
|
||||||
现有的开源微信 Markdown 编辑器,样式繁杂,也不符合我个人的审美需求。在我使用它们进行文章排版的时候,经常还要自己做一些改动,费时费力,因此动手做了二次开发。
|
现有的开源微信 Markdown 编辑器,样式繁杂,也不符合我个人的审美需求。在我使用它们进行内容排版的时候,经常还要自己做一些改动,费时费力,因此动手做了二次开发。
|
||||||
|
|
||||||
欢迎各位朋友随时提交 PR,让这款微信 Markdown 编辑器变得更好!如果你有新的想法,也欢迎在 [Discussions 讨论区](https://github.com/doocs/md/discussions)反馈。
|
欢迎各位朋友随时提交 PR,让这款微信 Markdown 编辑器变得更好!如果你有新的想法,也欢迎在 [Discussions 讨论区](https://github.com/doocs/md/discussions)反馈。
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章
|
|||||||
- [x] 允许自定义主题色和 CSS 样式,灵活定制展示效果
|
- [x] 允许自定义主题色和 CSS 样式,灵活定制展示效果
|
||||||
- [x] 提供多图上传功能,并可自定义配置图床
|
- [x] 提供多图上传功能,并可自定义配置图床
|
||||||
- [x] 便捷的文件导入、导出功能,提升工作效率
|
- [x] 便捷的文件导入、导出功能,提升工作效率
|
||||||
- [x] 内置本地文章管理功能,支持草稿自动保存
|
- [x] 内置本地内容管理功能,支持草稿自动保存
|
||||||
|
|
||||||
## 目前支持哪些图床
|
## 目前支持哪些图床
|
||||||
|
|
||||||
@ -65,8 +65,8 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章
|
|||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. 如果你使用了某些浏览器脚本修改了网页背景色,可能导致渲染后的文章出现背景色分块的现象,详见 [#63](https://github.com/doocs/md/issues/63)。
|
1. 如果你使用了某些浏览器脚本修改了网页背景色,可能导致渲染后的内容出现背景色分块的现象,详见 [#63](https://github.com/doocs/md/issues/63)。
|
||||||
2. 某些浏览器插件,会对文章样式造成破坏。现象是:复制粘贴到公众号后台文章,点击保存时,样式丢失,详见 [#151](https://github.com/doocs/md/issues/151)。
|
2. 某些浏览器插件,会对内容样式造成破坏。现象是:复制粘贴到公众号后台内容,点击保存时,样式丢失,详见 [#151](https://github.com/doocs/md/issues/151)。
|
||||||
|
|
||||||
## 自定义上传逻辑
|
## 自定义上传逻辑
|
||||||
|
|
||||||
@ -227,4 +227,4 @@ docker run -d -p 8080:80 doocs/md:latest
|
|||||||
- [程序员小宋](https://mp.weixin.qq.com/s/llgdqSN3AIXMlEbBuPkKNQ)
|
- [程序员小宋](https://mp.weixin.qq.com/s/llgdqSN3AIXMlEbBuPkKNQ)
|
||||||
- [架构师修行之路](https://mp.weixin.qq.com/s/-HWx7VZC6NthROGBaATcLA)
|
- [架构师修行之路](https://mp.weixin.qq.com/s/-HWx7VZC6NthROGBaATcLA)
|
||||||
|
|
||||||
注:如果你使用了本 Markdown 编辑器进行文章排版,并且希望在本项目 README 中展示你的公众号,请到 [#5](https://github.com/doocs/md/discussions/5) 留言。
|
注:如果你使用了本 Markdown 编辑器进行内容排版,并且希望在本项目 README 中展示你的公众号,请到 [#5](https://github.com/doocs/md/discussions/5) 留言。
|
||||||
|
@ -20,7 +20,7 @@ Markdown 是一种轻量级标记语言,用于格式化纯文本。它以简
|
|||||||
#### 四级标题
|
#### 四级标题
|
||||||
```
|
```
|
||||||
|
|
||||||
以上代码将渲染出一组层次分明的标题,使你的文章井井有条。
|
以上代码将渲染出一组层次分明的标题,使你的内容井井有条。
|
||||||
|
|
||||||
### 2. 段落与换行:自然流畅
|
### 2. 段落与换行:自然流畅
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ Markdown 中的段落就是一行接一行的文本。要创建新段落,只
|
|||||||
|
|
||||||
轻松实现富媒体内容展示!
|
轻松实现富媒体内容展示!
|
||||||
|
|
||||||
> 因微信公众号平台不支持除公众号文章以外的链接,故其他平台的链接,会呈现链接样式但无法点击跳转。
|
> 因微信公众号平台不支持除公众号内容以外的链接,故其他平台的链接,会呈现链接样式但无法点击跳转。
|
||||||
|
|
||||||
> 对于这些链接请注意明文书写,或点击左上角「格式->微信外链接转底部引用」开启引用,这样就可以在底部观察到链接指向。
|
> 对于这些链接请注意明文书写,或点击左上角「格式->微信外链接转底部引用」开启引用,这样就可以在底部观察到链接指向。
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ pie
|
|||||||
|
|
||||||
## 结语
|
## 结语
|
||||||
|
|
||||||
Markdown 是一种简单、强大且易于掌握的标记语言,通过学习基础和进阶语法,你可以快速创作内容并有效传达信息。无论是技术文档、个人博客还是项目说明,Markdown 都是你的得力助手。希望这篇文章能够带你全面了解 Markdown 的潜力,让你的写作更加丰富多彩!
|
Markdown 是一种简单、强大且易于掌握的标记语言,通过学习基础和进阶语法,你可以快速创作内容并有效传达信息。无论是技术文档、个人博客还是项目说明,Markdown 都是你的得力助手。希望这篇内容能够带你全面了解 Markdown 的潜力,让你的写作更加丰富多彩!
|
||||||
|
|
||||||
现在,拿起 Markdown 编辑器,开始创作吧!探索 Markdown 的世界,你会发现它远比想象中更精彩!
|
现在,拿起 Markdown 编辑器,开始创作吧!探索 Markdown 的世界,你会发现它远比想象中更精彩!
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ function tabChanged(tabName: string | number) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<transition enter-active-class="bounceInRight">
|
<transition enter-active-class="bounceInRight">
|
||||||
<div v-show="displayStore.isShowCssEditor" class="cssEditor-wrapper order-1 h-full flex flex-col border-l-1">
|
<div v-show="displayStore.isShowCssEditor" class="cssEditor-wrapper h-full flex flex-col border-l-1">
|
||||||
<Tabs
|
<Tabs
|
||||||
v-model="store.cssContentConfig.active"
|
v-model="store.cssContentConfig.active"
|
||||||
@update:model-value="tabChanged"
|
@update:model-value="tabChanged"
|
||||||
|
@ -33,7 +33,7 @@ function onRedirect(url: string) {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3>一款高度简洁的微信 Markdown 编辑器</h3>
|
<h3>一款高度简洁的微信 Markdown 编辑器</h3>
|
||||||
<p>扫码关注公众号 Doocs,原创技术文章第一时间推送!</p>
|
<p>扫码关注公众号 Doocs,原创技术内容第一时间推送!</p>
|
||||||
<img
|
<img
|
||||||
class="mx-auto my-5"
|
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"
|
src="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/1648303220922-7e14aefa-816e-44c1-8604-ade709ca1c69.png"
|
||||||
|
@ -1,39 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Format } from 'vue-pick-colors'
|
|
||||||
import { Toaster } from '@/components/ui/sonner'
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
import {
|
import {
|
||||||
altSign,
|
altSign,
|
||||||
codeBlockThemeOptions,
|
|
||||||
colorOptions,
|
|
||||||
ctrlKey,
|
ctrlKey,
|
||||||
ctrlSign,
|
ctrlSign,
|
||||||
fontFamilyOptions,
|
|
||||||
fontSizeOptions,
|
|
||||||
legendOptions,
|
|
||||||
shiftSign,
|
shiftSign,
|
||||||
themeOptions,
|
|
||||||
} from '@/config'
|
} from '@/config'
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useStore } from '@/stores'
|
||||||
import {
|
import { addPrefix, processClipboardContent } from '@/utils'
|
||||||
addPrefix,
|
import { ChevronDownIcon, PanelLeftClose, PanelLeftOpen, Settings } from 'lucide-vue-next'
|
||||||
processClipboardContent,
|
|
||||||
} from '@/utils'
|
|
||||||
import {
|
|
||||||
ChevronDownIcon,
|
|
||||||
Moon,
|
|
||||||
PanelLeftClose,
|
|
||||||
PanelLeftOpen,
|
|
||||||
Settings,
|
|
||||||
Sun,
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
import PickColors from 'vue-pick-colors'
|
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([`addFormat`, `formatContent`, `startCopy`, `endCopy`])
|
||||||
`addFormat`,
|
|
||||||
`formatContent`,
|
|
||||||
`startCopy`,
|
|
||||||
`endCopy`,
|
|
||||||
])
|
|
||||||
|
|
||||||
const formatItems = [
|
const formatItems = [
|
||||||
{
|
{
|
||||||
@ -69,9 +46,8 @@ const formatItems = [
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
|
||||||
|
|
||||||
const { isDark, isCiteStatus, isCountStatus, output, primaryColor } = storeToRefs(store)
|
const { isDark, isCiteStatus, isCountStatus, output, primaryColor, isOpenPostSlider } = storeToRefs(store)
|
||||||
|
|
||||||
const { toggleDark, editorRefresh, citeStatusChanged, countStatusChanged } = store
|
const { toggleDark, editorRefresh, citeStatusChanged, countStatusChanged } = store
|
||||||
|
|
||||||
@ -115,7 +91,7 @@ function copy() {
|
|||||||
toast.success(
|
toast.success(
|
||||||
copyMode.value === `html`
|
copyMode.value === `html`
|
||||||
? `已复制 HTML 源码,请进行下一步操作。`
|
? `已复制 HTML 源码,请进行下一步操作。`
|
||||||
: `已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴。`,
|
: `已复制渲染后的内容到剪贴板,可直接到公众号后台粘贴。`,
|
||||||
)
|
)
|
||||||
|
|
||||||
editorRefresh()
|
editorRefresh()
|
||||||
@ -123,54 +99,30 @@ function copy() {
|
|||||||
})
|
})
|
||||||
}, 350)
|
}, 350)
|
||||||
}
|
}
|
||||||
|
|
||||||
function customStyle() {
|
|
||||||
displayStore.toggleShowCssEditor()
|
|
||||||
setTimeout(() => {
|
|
||||||
store.cssEditor!.refresh()
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickColorsContainer = useTemplateRef<HTMLElement | undefined>(
|
|
||||||
`pickColorsContainer`,
|
|
||||||
)
|
|
||||||
const format = ref<Format>(`rgb`)
|
|
||||||
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="header-container h-15 flex items-center px-5">
|
<header class="header-container h-15 flex items-center justify-between px-5">
|
||||||
<Menubar class="menubar mr-auto">
|
<div class="space-x-2 flex">
|
||||||
|
<Menubar class="menubar">
|
||||||
<FileDropdown />
|
<FileDropdown />
|
||||||
|
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger> 格式 </MenubarTrigger>
|
<MenubarTrigger> 格式 </MenubarTrigger>
|
||||||
<MenubarContent class="w-60" align="start">
|
<MenubarContent class="w-60" align="start">
|
||||||
<MenubarCheckboxItem
|
<MenubarCheckboxItem
|
||||||
v-for="{ label, kbd, emitArgs } in formatItems"
|
v-for="{ label, kbd, emitArgs } in formatItems" :key="label"
|
||||||
:key="label"
|
@click="emitArgs[0] === 'addFormat' ? $emit(emitArgs[0], emitArgs[1]) : $emit(emitArgs[0])"
|
||||||
@click="
|
|
||||||
emitArgs[0] === 'addFormat'
|
|
||||||
? $emit(emitArgs[0], emitArgs[1])
|
|
||||||
: $emit(emitArgs[0])
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<MenubarShortcut>
|
<MenubarShortcut>
|
||||||
<kbd
|
<kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9">
|
||||||
v-for="item in kbd"
|
|
||||||
:key="item"
|
|
||||||
class="mx-1 bg-gray-2 dark:bg-stone-9"
|
|
||||||
>
|
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</kbd>
|
</kbd>
|
||||||
</MenubarShortcut>
|
</MenubarShortcut>
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarCheckboxItem
|
<MenubarCheckboxItem :checked="isCiteStatus" @click="citeStatusChanged()">
|
||||||
:checked="isCiteStatus"
|
|
||||||
@click="citeStatusChanged()"
|
|
||||||
>
|
|
||||||
微信外链转底部引用
|
微信外链转底部引用
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
@ -186,350 +138,24 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|||||||
<StyleDropdown />
|
<StyleDropdown />
|
||||||
<HelpDropdown />
|
<HelpDropdown />
|
||||||
</Menubar>
|
</Menubar>
|
||||||
|
|
||||||
<Button
|
|
||||||
v-if="!store.isOpenPostSlider"
|
|
||||||
variant="outline"
|
|
||||||
class="mr-2"
|
|
||||||
@click="store.isOpenPostSlider = true"
|
|
||||||
>
|
|
||||||
<PanelLeftOpen class="size-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
v-else
|
|
||||||
variant="outline"
|
|
||||||
class="mr-2"
|
|
||||||
@click="store.isOpenPostSlider = false"
|
|
||||||
>
|
|
||||||
<PanelLeftClose class="size-4" />
|
|
||||||
</Button>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger>
|
|
||||||
<Button variant="outline">
|
|
||||||
<Settings class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent class="h-100 w-100 overflow-auto px-6" align="end">
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>主题</h2>
|
|
||||||
<div class="grid grid-cols-3 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="{ label, value } in themeOptions"
|
|
||||||
:key="value"
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.theme === value,
|
|
||||||
}"
|
|
||||||
@click="store.themeChanged(value)"
|
|
||||||
>
|
|
||||||
{{ label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>字体</h2>
|
|
||||||
<div class="grid grid-cols-3 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="{ label, value } in fontFamilyOptions"
|
|
||||||
:key="value"
|
|
||||||
variant="outline"
|
|
||||||
class="w-full"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.fontFamily === value,
|
|
||||||
}"
|
|
||||||
@click="store.fontChanged(value)"
|
|
||||||
>
|
|
||||||
{{ label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>字号</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="{ value, desc } in fontSizeOptions"
|
|
||||||
:key="value"
|
|
||||||
variant="outline"
|
|
||||||
class="w-full"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.fontSize === value,
|
|
||||||
}"
|
|
||||||
@click="store.sizeChanged(value)"
|
|
||||||
>
|
|
||||||
{{ desc }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>主题色</h2>
|
|
||||||
<div class="grid grid-cols-3 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="{ label, value } in colorOptions"
|
|
||||||
:key="value"
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white':
|
|
||||||
store.primaryColor === value,
|
|
||||||
}"
|
|
||||||
@click="store.colorChanged(value)"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="mr-2 inline-block h-4 w-4 rounded-full"
|
|
||||||
:style="{
|
|
||||||
background: value,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
{{ label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>自定义主题色</h2>
|
|
||||||
<div ref="pickColorsContainer">
|
|
||||||
<PickColors
|
|
||||||
v-if="pickColorsContainer"
|
|
||||||
v-model:value="primaryColor"
|
|
||||||
show-alpha
|
|
||||||
:format="format"
|
|
||||||
:format-options="formatOptions"
|
|
||||||
:theme="store.isDark ? 'dark' : 'light'"
|
|
||||||
:popup-container="pickColorsContainer"
|
|
||||||
@change="store.colorChanged"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>代码块主题</h2>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
v-model="store.codeBlockTheme"
|
|
||||||
@update:model-value="store.codeBlockThemeChanged"
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a fruit" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem
|
|
||||||
v-for="{ label, value } in codeBlockThemeOptions"
|
|
||||||
:key="label"
|
|
||||||
:value="value"
|
|
||||||
>
|
|
||||||
{{ label }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>图注格式</h2>
|
|
||||||
<div class="grid grid-cols-3 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="{ label, value } in legendOptions"
|
|
||||||
:key="value"
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.legend === value,
|
|
||||||
}"
|
|
||||||
@click="store.legendChanged(value)"
|
|
||||||
>
|
|
||||||
{{ label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-x-2 flex">
|
||||||
<h2>Mac 代码块</h2>
|
<TooltipProvider :delay-duration="200">
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger as-child>
|
||||||
class="w-full"
|
<Button variant="outline" @click="isOpenPostSlider = !isOpenPostSlider">
|
||||||
variant="outline"
|
<PanelLeftOpen v-show="!isOpenPostSlider" class="size-4" />
|
||||||
:class="{
|
<PanelLeftClose v-show="isOpenPostSlider" class="size-4" />
|
||||||
'border-black dark:border-white': store.isMacCodeBlock,
|
|
||||||
}"
|
|
||||||
@click="!store.isMacCodeBlock && store.macCodeBlockChanged()"
|
|
||||||
>
|
|
||||||
开启
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
</TooltipTrigger>
|
||||||
class="w-full"
|
<TooltipContent side="left">
|
||||||
variant="outline"
|
{{ isOpenPostSlider ? "关闭" : "内容管理" }}
|
||||||
:class="{
|
</TooltipContent>
|
||||||
'border-black dark:border-white': !store.isMacCodeBlock,
|
</Tooltip>
|
||||||
}"
|
</TooltipProvider>
|
||||||
@click="store.isMacCodeBlock && store.macCodeBlockChanged()"
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>微信外链转底部引用</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.isCiteStatus,
|
|
||||||
}"
|
|
||||||
@click="!store.isCiteStatus && store.citeStatusChanged()"
|
|
||||||
>
|
|
||||||
开启
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !store.isCiteStatus,
|
|
||||||
}"
|
|
||||||
@click="store.isCiteStatus && store.citeStatusChanged()"
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>统计字数和阅读时间</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.isCountStatus,
|
|
||||||
}"
|
|
||||||
@click="!store.isCountStatus && store.countStatusChanged()"
|
|
||||||
>
|
|
||||||
开启
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !store.isCountStatus,
|
|
||||||
}"
|
|
||||||
@click="store.isCountStatus && store.countStatusChanged()"
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>段落首行缩进</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.isUseIndent,
|
|
||||||
}"
|
|
||||||
@click="!store.isUseIndent && store.useIndentChanged()"
|
|
||||||
>
|
|
||||||
开启
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !store.isUseIndent,
|
|
||||||
}"
|
|
||||||
@click="store.isUseIndent && store.useIndentChanged()"
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>自定义 CSS 面板</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white':
|
|
||||||
displayStore.isShowCssEditor,
|
|
||||||
}"
|
|
||||||
@click="!displayStore.isShowCssEditor && customStyle()"
|
|
||||||
>
|
|
||||||
开启
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !displayStore.isShowCssEditor,
|
|
||||||
}"
|
|
||||||
@click="displayStore.isShowCssEditor && customStyle()"
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>编辑区位置</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': store.isEditOnLeft,
|
|
||||||
}"
|
|
||||||
@click="!store.isEditOnLeft && store.toggleEditOnLeft()"
|
|
||||||
>
|
|
||||||
左侧
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !store.isEditOnLeft,
|
|
||||||
}"
|
|
||||||
@click="store.isEditOnLeft && store.toggleEditOnLeft()"
|
|
||||||
>
|
|
||||||
右侧
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>模式</h2>
|
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': !isDark,
|
|
||||||
}"
|
|
||||||
@click="store.toggleDark(false)"
|
|
||||||
>
|
|
||||||
<Sun class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="outline"
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white': isDark,
|
|
||||||
}"
|
|
||||||
@click="store.toggleDark(true)"
|
|
||||||
>
|
|
||||||
<Moon class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<h2>样式配置</h2>
|
|
||||||
<Button @click="store.resetStyleConfirm">
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<div
|
<div class="space-x-1 bg-background text-background-foreground mx-2 flex items-center border rounded-md">
|
||||||
class="space-x-1 bg-background text-background-foreground mx-2 flex items-center border rounded-md"
|
|
||||||
>
|
|
||||||
<Button variant="ghost" class="shadow-none" @click="copy">
|
<Button variant="ghost" class="shadow-none" @click="copy">
|
||||||
复制
|
复制
|
||||||
</Button>
|
</Button>
|
||||||
@ -540,7 +166,11 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|||||||
<ChevronDownIcon class="text-secondary-foreground h-4 w-4" />
|
<ChevronDownIcon class="text-secondary-foreground h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" :align-offset="-5" class="w-[200px]">
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
:align-offset="-5"
|
||||||
|
class="w-[200px]"
|
||||||
|
>
|
||||||
<DropdownMenuRadioGroup v-model="copyMode">
|
<DropdownMenuRadioGroup v-model="copyMode">
|
||||||
<DropdownMenuRadioItem value="txt">
|
<DropdownMenuRadioItem value="txt">
|
||||||
公众号格式
|
公众号格式
|
||||||
@ -555,7 +185,12 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|||||||
|
|
||||||
<PostInfo />
|
<PostInfo />
|
||||||
|
|
||||||
|
<Button variant="outline" @click="store.isOpenRightSlider = !store.isOpenRightSlider">
|
||||||
|
<Settings class="size-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Toaster rich-colors position="top-center" />
|
<Toaster rich-colors position="top-center" />
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ watch(isOpen, () => {
|
|||||||
|
|
||||||
function addPost() {
|
function addPost() {
|
||||||
if (addPostInputVal.value === ``) {
|
if (addPostInputVal.value === ``) {
|
||||||
toast.error(`文章标题不可为空`)
|
toast.error(`内容标题不可为空`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.addPost(addPostInputVal.value)
|
store.addPost(addPostInputVal.value)
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
toast.success(`文章新增成功`)
|
toast.success(`内容新增成功`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editTarget = ref(-1)
|
const editTarget = ref(-1)
|
||||||
@ -35,12 +35,12 @@ function startRenamePost(index: number) {
|
|||||||
|
|
||||||
function renamePost() {
|
function renamePost() {
|
||||||
if (renamePostInputVal.value === ``) {
|
if (renamePostInputVal.value === ``) {
|
||||||
toast.error(`文章标题不可为空`)
|
toast.error(`内容标题不可为空`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.renamePost(editTarget.value, renamePostInputVal.value)
|
store.renamePost(editTarget.value, renamePostInputVal.value)
|
||||||
isOpenEditDialog.value = false
|
isOpenEditDialog.value = false
|
||||||
toast.success(`文章重命名成功`)
|
toast.success(`内容重命名成功`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOpenDelPostConfirmDialog = ref(false)
|
const isOpenDelPostConfirmDialog = ref(false)
|
||||||
@ -51,30 +51,36 @@ function startDelPost(index: number) {
|
|||||||
function delPost() {
|
function delPost() {
|
||||||
store.delPost(editTarget.value)
|
store.delPost(editTarget.value)
|
||||||
isOpenDelPostConfirmDialog.value = false
|
isOpenDelPostConfirmDialog.value = false
|
||||||
toast.success(`文章删除成功`)
|
toast.success(`内容删除成功`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden border-r bg-gray/20 transition-width dark:bg-gray/40"
|
class="overflow-hidden bg-gray/20 transition-width duration-300 dark:bg-gray/40"
|
||||||
:class="{
|
:class="{
|
||||||
'w-0': !store.isOpenPostSlider,
|
'w-0': !store.isOpenPostSlider,
|
||||||
'w-50': store.isOpenPostSlider,
|
'w-50': store.isOpenPostSlider,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<nav class="space-y-1 h-full overflow-auto p-2">
|
<nav
|
||||||
|
class="space-y-1 h-full overflow-auto p-2 transition-transform"
|
||||||
|
:class="{
|
||||||
|
'translate-x-100': store.isOpenPostSlider,
|
||||||
|
'-translate-x-full': !store.isOpenPostSlider,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<Dialog v-model:open="isOpen">
|
<Dialog v-model:open="isOpen">
|
||||||
<DialogTrigger as-child>
|
<DialogTrigger as-child>
|
||||||
<Button variant="outline" class="w-full" size="xs">
|
<Button variant="outline" class="w-full" size="xs">
|
||||||
<Plus /> 新增文章
|
<Plus /> 新增内容
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>新增文章</DialogTitle>
|
<DialogTitle>新增内容</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
请输入文章名称
|
请输入内容名称
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Input v-model="addPostInputVal" />
|
<Input v-model="addPostInputVal" />
|
||||||
@ -86,10 +92,7 @@ function delPost() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<a
|
<a
|
||||||
v-for="(post, index) in store.posts"
|
v-for="(post, index) in store.posts" :key="post.title" href="#" :class="{
|
||||||
:key="post.title"
|
|
||||||
href="#"
|
|
||||||
:class="{
|
|
||||||
'bg-primary text-primary-foreground shadow-lg border-2 border-primary': store.currentPostIndex === index,
|
'bg-primary text-primary-foreground shadow-lg border-2 border-primary': store.currentPostIndex === index,
|
||||||
'dark:bg-primary-dark dark:text-primary-foreground-dark dark:border-primary-dark': store.currentPostIndex === index,
|
'dark:bg-primary-dark dark:text-primary-foreground-dark dark:border-primary-dark': store.currentPostIndex === index,
|
||||||
}"
|
}"
|
||||||
@ -119,9 +122,9 @@ function delPost() {
|
|||||||
<Dialog v-model:open="isOpenEditDialog">
|
<Dialog v-model:open="isOpenEditDialog">
|
||||||
<DialogContent class="sm:max-w-[425px]">
|
<DialogContent class="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>编辑文章名称</DialogTitle>
|
<DialogTitle>编辑内容名称</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
请输入新的文章名称
|
请输入新的内容名称
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Input v-model="renamePostInputVal" />
|
<Input v-model="renamePostInputVal" />
|
||||||
@ -141,7 +144,7 @@ function delPost() {
|
|||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>提示</AlertDialogTitle>
|
<AlertDialogTitle>提示</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
此操作将删除该文章,是否继续?
|
此操作将删除该内容,是否继续?
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
|
276
src/components/CodemirrorEditor/RightSlider.vue
Normal file
276
src/components/CodemirrorEditor/RightSlider.vue
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
codeBlockThemeOptions,
|
||||||
|
colorOptions,
|
||||||
|
fontFamilyOptions,
|
||||||
|
fontSizeOptions,
|
||||||
|
legendOptions,
|
||||||
|
themeOptions,
|
||||||
|
} from '@/config'
|
||||||
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
|
import { Moon, Sun } from 'lucide-vue-next'
|
||||||
|
import PickColors, { type Format } from 'vue-pick-colors'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const displayStore = useDisplayStore()
|
||||||
|
|
||||||
|
const { isDark, primaryColor } = storeToRefs(store)
|
||||||
|
|
||||||
|
function customStyle() {
|
||||||
|
displayStore.toggleShowCssEditor()
|
||||||
|
setTimeout(() => {
|
||||||
|
store.cssEditor!.refresh()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOpen = ref(false)
|
||||||
|
|
||||||
|
const addPostInputVal = ref(``)
|
||||||
|
|
||||||
|
watch(isOpen, () => {
|
||||||
|
if (isOpen.value) {
|
||||||
|
addPostInputVal.value = ``
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const pickColorsContainer = useTemplateRef<HTMLElement | undefined>(`pickColorsContainer`)
|
||||||
|
const format = ref<Format>(`rgb`)
|
||||||
|
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="overflow-hidden bg-gray/20 transition-width duration-300 dark:bg-gray/40"
|
||||||
|
:class="{
|
||||||
|
'w-0': !store.isOpenRightSlider,
|
||||||
|
'w-100': store.isOpenRightSlider,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="space-y-4 h-full overflow-auto p-4 transition-transform" :class="{
|
||||||
|
'translate-x-0': store.isOpenRightSlider,
|
||||||
|
'translate-x-full': !store.isOpenRightSlider,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>主题</h2>
|
||||||
|
<div class="grid grid-cols-3 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-for="{ label, value } in themeOptions" :key="value" class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.theme === value,
|
||||||
|
}" @click="store.themeChanged(value)"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>字体</h2>
|
||||||
|
<div class="grid grid-cols-3 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-for="{ label, value } in fontFamilyOptions" :key="value" variant="outline" class="w-full"
|
||||||
|
:class="{ 'border-black dark:border-white': store.fontFamily === value }"
|
||||||
|
@click="store.fontChanged(value)"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>字号</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-for="{ value, desc } in fontSizeOptions" :key="value" variant="outline" class="w-full" :class="{
|
||||||
|
'border-black dark:border-white': store.fontSize === value,
|
||||||
|
}" @click="store.sizeChanged(value)"
|
||||||
|
>
|
||||||
|
{{ desc }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>主题色</h2>
|
||||||
|
<div class="grid grid-cols-3 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-for="{ label, value } in colorOptions" :key="value" class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.primaryColor === value,
|
||||||
|
}" @click="store.colorChanged(value)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mr-2 inline-block h-4 w-4 rounded-full" :style="{
|
||||||
|
background: value,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
{{ label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>自定义主题色</h2>
|
||||||
|
<div ref="pickColorsContainer">
|
||||||
|
<PickColors
|
||||||
|
v-if="pickColorsContainer"
|
||||||
|
v-model:value="primaryColor"
|
||||||
|
show-alpha :format="format"
|
||||||
|
:format-options="formatOptions"
|
||||||
|
:theme="store.isDark ? 'dark' : 'light'"
|
||||||
|
:popup-container="pickColorsContainer"
|
||||||
|
@change="store.colorChanged"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>代码块主题</h2>
|
||||||
|
<div>
|
||||||
|
<Select v-model="store.codeBlockTheme" @update:model-value="store.codeBlockThemeChanged">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem v-for="{ label, value } in codeBlockThemeOptions" :key="label" :value="value">
|
||||||
|
{{ label }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>图注格式</h2>
|
||||||
|
<div class="grid grid-cols-3 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
v-for="{ label, value } in legendOptions" :key="value" class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.legend === value,
|
||||||
|
}" @click="store.legendChanged(value)"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>Mac 代码块</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.isMacCodeBlock,
|
||||||
|
}" @click="!store.isMacCodeBlock && store.macCodeBlockChanged()"
|
||||||
|
>
|
||||||
|
开启
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !store.isMacCodeBlock,
|
||||||
|
}" @click="store.isMacCodeBlock && store.macCodeBlockChanged()"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>微信外链转底部引用</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.isCiteStatus,
|
||||||
|
}" @click="!store.isCiteStatus && store.citeStatusChanged()"
|
||||||
|
>
|
||||||
|
开启
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !store.isCiteStatus,
|
||||||
|
}" @click="store.isCiteStatus && store.citeStatusChanged()"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>段落首行缩进</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.isUseIndent,
|
||||||
|
}" @click="!store.isUseIndent && store.useIndentChanged()"
|
||||||
|
>
|
||||||
|
开启
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !store.isUseIndent,
|
||||||
|
}" @click="store.isUseIndent && store.useIndentChanged()"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>自定义 CSS 面板</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': displayStore.isShowCssEditor,
|
||||||
|
}" @click="!displayStore.isShowCssEditor && customStyle()"
|
||||||
|
>
|
||||||
|
开启
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !displayStore.isShowCssEditor,
|
||||||
|
}" @click="displayStore.isShowCssEditor && customStyle()"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>编辑区位置</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': store.isEditOnLeft,
|
||||||
|
}" @click="!store.isEditOnLeft && store.toggleEditOnLeft()"
|
||||||
|
>
|
||||||
|
左侧
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !store.isEditOnLeft,
|
||||||
|
}" @click="store.isEditOnLeft && store.toggleEditOnLeft()"
|
||||||
|
>
|
||||||
|
右侧
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>模式</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': !isDark,
|
||||||
|
}" @click="store.toggleDark(false)"
|
||||||
|
>
|
||||||
|
<Sun class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full" variant="outline" :class="{
|
||||||
|
'border-black dark:border-white': isDark,
|
||||||
|
}" @click="store.toggleDark(true)"
|
||||||
|
>
|
||||||
|
<Moon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>样式配置</h2>
|
||||||
|
<Button @click="store.resetStyleConfirm">
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
|
||||||
|
</style>
|
14
src/components/ui/tooltip/Tooltip.vue
Normal file
14
src/components/ui/tooltip/Tooltip.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<TooltipRootProps>()
|
||||||
|
const emits = defineEmits<TooltipRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TooltipRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</TooltipRoot>
|
||||||
|
</template>
|
31
src/components/ui/tooltip/TooltipContent.vue
Normal file
31
src/components/ui/tooltip/TooltipContent.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TooltipContent, type TooltipContentEmits, type TooltipContentProps, TooltipPortal, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes[`class`] }>(), {
|
||||||
|
sideOffset: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits<TooltipContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent v-bind="{ ...forwarded, ...$attrs }" :class="cn('z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)">
|
||||||
|
<slot />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</template>
|
11
src/components/ui/tooltip/TooltipProvider.vue
Normal file
11
src/components/ui/tooltip/TooltipProvider.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { TooltipProvider, type TooltipProviderProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<TooltipProviderProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TooltipProvider v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</TooltipProvider>
|
||||||
|
</template>
|
11
src/components/ui/tooltip/TooltipTrigger.vue
Normal file
11
src/components/ui/tooltip/TooltipTrigger.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { TooltipTrigger, type TooltipTriggerProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<TooltipTriggerProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TooltipTrigger v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</TooltipTrigger>
|
||||||
|
</template>
|
4
src/components/ui/tooltip/index.ts
Normal file
4
src/components/ui/tooltip/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as Tooltip } from './Tooltip.vue'
|
||||||
|
export { default as TooltipContent } from './TooltipContent.vue'
|
||||||
|
export { default as TooltipProvider } from './TooltipProvider.vue'
|
||||||
|
export { default as TooltipTrigger } from './TooltipTrigger.vue'
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>公众号文章编辑器</title>
|
<title>公众号内容编辑器</title>
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -56,13 +56,15 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
// 预备弃用
|
// 预备弃用
|
||||||
const editorContent = useStorage(`__editor_content`, DEFAULT_CONTENT)
|
const editorContent = useStorage(`__editor_content`, DEFAULT_CONTENT)
|
||||||
|
|
||||||
|
const isOpenRightSlider = useStorage(addPrefix(`is_open_right_slider`), false)
|
||||||
|
|
||||||
const isOpenPostSlider = useStorage(addPrefix(`is_open_post_slider`), false)
|
const isOpenPostSlider = useStorage(addPrefix(`is_open_post_slider`), false)
|
||||||
// 文章列表
|
// 内容列表
|
||||||
const posts = useStorage(addPrefix(`posts`), [{
|
const posts = useStorage(addPrefix(`posts`), [{
|
||||||
title: `文章1`,
|
title: `内容1`,
|
||||||
content: DEFAULT_CONTENT,
|
content: DEFAULT_CONTENT,
|
||||||
}])
|
}])
|
||||||
// 当前文章
|
// 当前内容
|
||||||
const currentPostIndex = useStorage(addPrefix(`current_post_index`), 0)
|
const currentPostIndex = useStorage(addPrefix(`current_post_index`), 0)
|
||||||
|
|
||||||
const addPost = (title: string) => {
|
const addPost = (title: string) => {
|
||||||
@ -296,7 +298,7 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
fontFamily.value = fontFamilyOptions[0].value
|
fontFamily.value = fontFamilyOptions[0].value
|
||||||
fontSize.value = fontSizeOptions[2].value
|
fontSize.value = fontSizeOptions[2].value
|
||||||
primaryColor.value = colorOptions[0].value
|
primaryColor.value = colorOptions[0].value
|
||||||
codeBlockTheme.value = codeBlockThemeOptions[2].value
|
codeBlockTheme.value = codeBlockThemeOptions[23].value
|
||||||
legend.value = legendOptions[3].value
|
legend.value = legendOptions[3].value
|
||||||
|
|
||||||
cssContentConfig.value = {
|
cssContentConfig.value = {
|
||||||
@ -492,6 +494,7 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
renamePost,
|
renamePost,
|
||||||
delPost,
|
delPost,
|
||||||
isOpenPostSlider,
|
isOpenPostSlider,
|
||||||
|
isOpenRightSlider,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import CodeMirror from 'codemirror'
|
|||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
const displayStore = useDisplayStore()
|
||||||
const { isDark, output, editor, readingTime } = storeToRefs(store)
|
const { isDark, output, editor, readingTime } = storeToRefs(store)
|
||||||
const { isShowCssEditor } = storeToRefs(displayStore)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
editorRefresh,
|
editorRefresh,
|
||||||
@ -363,18 +362,30 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="container" class="container flex flex-col">
|
<div ref="container" class="container flex flex-col">
|
||||||
<EditorHeader @add-format="addFormat" @format-content="formatContent" @start-copy="startCopy" @end-copy="endCopy" />
|
<EditorHeader
|
||||||
<main class="container-main flex-1">
|
@add-format="addFormat"
|
||||||
<div class="container-main-section h-full flex border-1">
|
@format-content="formatContent"
|
||||||
|
@start-copy="startCopy"
|
||||||
|
@end-copy="endCopy"
|
||||||
|
/>
|
||||||
|
<main class="container-main flex flex-1 flex-col">
|
||||||
|
<div class="container-main-section border-radius-10 relative flex flex-1 overflow-hidden border-1">
|
||||||
<PostSlider />
|
<PostSlider />
|
||||||
<div
|
<div
|
||||||
ref="codeMirrorWrapper" class="codeMirror-wrapper flex-1 border-r-1" :class="{
|
ref="codeMirrorWrapper"
|
||||||
'order-1': !store.isEditOnLeft,
|
class="codeMirror-wrapper flex-1"
|
||||||
|
:class="{
|
||||||
|
'order-1 border-l': !store.isEditOnLeft,
|
||||||
|
'border-r': store.isEditOnLeft,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<textarea id="editor" type="textarea" placeholder="Your markdown text here." />
|
<textarea
|
||||||
|
id="editor"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="Your markdown text here."
|
||||||
|
/>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent class="w-64">
|
<ContextMenuContent class="w-64">
|
||||||
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
|
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
|
||||||
@ -403,9 +414,13 @@ onMounted(() => {
|
|||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview" ref="preview" :span="isShowCssEditor ? 8 : 12" class="preview-wrapper flex-1 p-5">
|
<div
|
||||||
|
id="preview"
|
||||||
|
ref="preview"
|
||||||
|
class="preview-wrapper flex-1 p-5"
|
||||||
|
>
|
||||||
<div id="output-wrapper" :class="{ output_night: !backLight }">
|
<div id="output-wrapper" :class="{ output_night: !backLight }">
|
||||||
<div class="preview border shadow-xl">
|
<div class="preview border-x-1 shadow-xl">
|
||||||
<section id="output" v-html="output" />
|
<section id="output" v-html="output" />
|
||||||
<div v-if="isCoping" class="loading-mask">
|
<div v-if="isCoping" class="loading-mask">
|
||||||
<div class="loading-mask-box">
|
<div class="loading-mask-box">
|
||||||
@ -416,9 +431,10 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CssEditor class="flex-1" />
|
<CssEditor class="order-2 flex-1" />
|
||||||
|
<RightSlider class="order-2" />
|
||||||
</div>
|
</div>
|
||||||
<footer class="flex flex-1 justify-end pr-5 text-[12px]">
|
<footer class="h-[30px] flex select-none items-center justify-end text-[12px]">
|
||||||
字数 {{ readingTime?.words }}, 阅读大约需 {{ Math.ceil(readingTime?.minutes ?? 0) }} 分钟
|
字数 {{ readingTime?.words }}, 阅读大约需 {{ Math.ceil(readingTime?.minutes ?? 0) }} 分钟
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
@ -461,8 +477,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.container-main {
|
.container-main {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 20px;
|
padding: 0 20px;
|
||||||
padding-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#output-wrapper {
|
#output-wrapper {
|
||||||
|
@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
publicDir: `../public`,
|
publicDir: `../public`,
|
||||||
extensionApi: `chrome`,
|
extensionApi: `chrome`,
|
||||||
manifest: {
|
manifest: {
|
||||||
name: `公众号文章编辑器`,
|
name: `公众号内容编辑器`,
|
||||||
description: `一款高度简洁的微信 Markdown 编辑器:支持 Markdown 语法、色盘取色、多图上传、一键下载文档、自定义 CSS 样式、一键重置等特性`,
|
description: `一款高度简洁的微信 Markdown 编辑器:支持 Markdown 语法、色盘取色、多图上传、一键下载文档、自定义 CSS 样式、一键重置等特性`,
|
||||||
version: `0.0.7`,
|
version: `0.0.7`,
|
||||||
icons: {
|
icons: {
|
||||||
|
Loading…
Reference in New Issue
Block a user