feat: support for theme options (#326)

close #322

---------

Co-Authored-By: brzhang <1595819400@qq.com>
This commit is contained in:
YangFong 2024-08-19 11:09:09 +08:00
parent 900a7a1ca9
commit c4c7b7fed7
4 changed files with 324 additions and 20 deletions

View File

@ -18,12 +18,13 @@ import {
HoverCardTrigger,
} from '@/components/ui/hover-card'
import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, githubConfig, legendOptions } from '@/config'
import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, githubConfig, legendOptions, themeOptions } from '@/config'
import { useStore } from '@/stores'
const store = useStore()
const {
theme,
fontFamily,
fontSize,
fontColor,
@ -35,6 +36,7 @@ const {
const {
resetStyleConfirm,
themeChanged,
fontChanged,
sizeChanged,
colorChanged,
@ -73,6 +75,8 @@ function customStyle() {
</el-icon>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56">
<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" />
<StyleOptionMenu
@ -133,7 +137,7 @@ function customStyle() {
Mac 代码块
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem divided class="leading-8" @click="resetStyleConfirm">
<DropdownMenuItem divided @click="resetStyleConfirm">
<el-icon class="mr-2 h-4 w-4" />
重置
</DropdownMenuItem>

View File

@ -176,6 +176,7 @@ export const giteeConfig = {
}
const baseColor = `#3f3f3f`
const baseBorderColor = `rgba(215, 16, 166, 0.8)`
export const defaultTheme = {
BASE: {
@ -368,3 +369,270 @@ export const defaultTheme = {
},
},
}
export const graceTheme = {
BASE: {
'text-align': `left`,
'line-height': `1.75`,
},
block: {
'h1': {
'font-size': `1.4em`,
'text-align': `center`,
'font-weight': `bold`,
'display': `table`,
'margin': `2em auto 1em`,
'padding': `0.5em 1em`,
'border-bottom': `2px solid ${baseBorderColor}`,
'color': `var(--el-text-color-regular)`,
'text-shadow': `2px 2px 4px rgba(0,0,0,0.1)`,
'transition': `all 0.3s ease`,
'&:hover': {
'transform': `translateY(-3px)`,
'box-shadow': `0 10px 20px rgba(0,0,0,0.1)`,
},
},
'h2': {
'font-size': `1.3em`,
'text-align': `center`,
'font-weight': `bold`,
'display': `table`,
'margin': `4em auto 2em`,
'padding': `0.3em 1em`,
'background': `${baseBorderColor}`,
'color': `#fff`,
'border-radius': `8px`,
'box-shadow': `0 4px 6px rgba(0,0,0,0.1)`,
'transition': `all 0.3s ease`,
'&:hover': {
'transform': `scale(1.05)`,
'box-shadow': `0 6px 8px rgba(0,0,0,0.15)`,
},
},
'h3': {
'font-weight': `bold`,
'font-size': `1.2em`,
'margin': `2em 8px 0.75em 0`,
'line-height': `1.2`,
'padding-left': `12px`,
'border-left': `4px solid ${baseBorderColor}`,
'border-bottom': `1px solid ${baseBorderColor}`,
'color': `var(--el-text-color-regular)`,
'transition': `all 0.3s ease`,
'&:hover': {
'padding-left': `16px`,
'border-left-width': `6px`,
},
},
'h4': {
'font-weight': `bold`,
'font-size': `1.1em`,
'margin': `2em 8px 0.5em`,
'color': `rgba(66, 185, 131, 0.9)`,
'transition': `color 0.3s ease`,
'&:hover': {
color: `rgba(66, 185, 131, 1)`,
},
},
'p': {
'margin': `1.5em 8px`,
'letter-spacing': `0.1em`,
'color': `var(--el-text-color-regular)`,
'text-align': `justify`,
'transition': `all 0.3s ease`,
'&:hover': {
transform: `translateX(3px)`,
},
},
'blockquote': {
'font-style': `italic`,
'border-left': `4px solid ${baseBorderColor}`,
'padding': `1em 1em 1em 2em`,
'border-radius': `6px`,
'color': `rgba(0,0,0,0.6)`,
'background': `linear-gradient(to right, #f7f7f7, #ffffff)`,
'margin': `2em 8px`,
'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`,
'transition': `all 0.3s ease`,
'&:hover': {
'transform': `translateY(-3px)`,
'box-shadow': `0 6px 8px rgba(0,0,0,0.1)`,
},
},
'blockquote_p': {
'letter-spacing': `0.1em`,
'color': `rgb(80, 80, 80)`,
'font-size': `1em`,
'display': `block`,
},
'code_pre': {
'font-size': `14px`,
'overflow-x': `auto`,
'border-radius': `8px`,
'padding': `1em`,
'line-height': `1.5`,
'margin': `10px 8px`,
'box-shadow': `inset 0 0 10px rgba(0,0,0,0.05)`,
'transition': `all 0.3s ease`,
'&:hover': {
'box-shadow': `inset 0 0 15px rgba(0,0,0,0.1)`,
},
},
'code': {
'margin': 0,
'white-space': `pre-wrap`,
'font-family': `'Fira Code', Menlo, Operator Mono, Consolas, Monaco, monospace`,
},
'image': {
'border-radius': `8px`,
'display': `block`,
'margin': `0.1em auto 0.5em`,
'width': `100% !important`,
'box-shadow': `0 4px 8px rgba(0,0,0,0.1)`,
'transition': `all 0.3s ease`,
'&:hover': {
'transform': `scale(1.02)`,
'box-shadow': `0 6px 12px rgba(0,0,0,0.15)`,
},
},
'ol': {
'margin-left': `0`,
'padding-left': `1.5em`,
'color': `var(--el-text-color-regular)`,
},
'ul': {
'margin-left': `0`,
'padding-left': `1.5em`,
'list-style': `none`,
'color': `var(--el-text-color-regular)`,
},
'ul li::before': {
'content': `"•"`,
'color': `rgba(0, 152, 116, 0.9)`,
'font-weight': `bold`,
'display': `inline-block`,
'width': `1em`,
'margin-left': `-1em`,
},
'hr': {
border: `none`,
height: `1px`,
background: `linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.1), rgba(0,0,0,0))`,
margin: `2em 0`,
},
},
inline: {
'listitem': {
'text-indent': `-1em`,
'display': `block`,
'margin': `0.5em 8px`,
'color': `var(--el-text-color-regular)`,
'transition': `all 0.3s ease`,
'&:hover': {
transform: `translateX(5px)`,
},
},
'codespan': {
'font-size': `90%`,
'color': `#d14`,
'background': `rgba(27,31,35,.05)`,
'padding': `3px 5px`,
'border-radius': `4px`,
'transition': `all 0.3s ease`,
'&:hover': {
background: `rgba(27,31,35,.1)`,
},
},
'link': {
'color': `#576b95`,
'transition': `all 0.3s ease`,
'&:hover': {
'color': `#1a3f6f`,
'text-decoration': `underline`,
},
},
'wx_link': {
},
'strong': {
'color': `rgba(15, 76, 129, 0.9)`,
'font-weight': `bold`,
'transition': `all 0.3s ease`,
'&:hover': {
'color': `rgba(15, 76, 129, 1)`,
'text-shadow': `1px 1px 2px rgba(15, 76, 129, 0.2)`,
},
},
'table': {
'border-collapse': `separate`,
'border-spacing': `0`,
'text-align': `center`,
'margin': `1em 8px`,
'color': `var(--el-text-color-regular)`,
'box-shadow': `0 4px 6px rgba(0,0,0,0.1)`,
'border-radius': `8px`,
'overflow': `hidden`,
},
'thead': {
'background': `linear-gradient(45deg, rgba(0, 152, 116, 0.9), rgba(0, 192, 146, 0.9))`,
'color': `#fff`,
'font-weight': `bold`,
},
'td': {
border: `1px solid #dfdfdf`,
padding: `0.5em 1em`,
color: baseColor,
transition: `all 0.3s ease`,
},
'tr:hover td': {
background: `rgba(0, 152, 116, 0.05)`,
},
'footnote': {
'font-size': `12px`,
'color': `rgba(0,0,0,0.5)`,
'transition': `all 0.3s ease`,
'&:hover': {
color: `rgba(0,0,0,0.7)`,
},
},
},
}
export const themeOptions = [
{
label: `经典`,
value: `default`,
desc: ``,
},
{
label: `优雅`,
value: `grace`,
desc: ``,
},
]
export const themeMap = {
default: defaultTheme,
grace: graceTheme,
}

View File

@ -5,11 +5,11 @@ import CodeMirror from 'codemirror'
import { useDark, useStorage, useToggle } from '@vueuse/core'
import { ElMessage, ElMessageBox } from 'element-plus'
import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions } from '@/config'
import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, themeMap, themeOptions } from '@/config'
import WxRenderer from '@/utils/wx-renderer'
import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw'
import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw'
import { addPrefix, css2json, customCssWithTemplate, downloadMD, exportHTML, formatCss, formatDoc, setColor, setColorWithCustomTemplate, setFontSize } from '@/utils'
import { addPrefix, css2json, customCssWithTemplate, downloadMD, exportHTML, formatCss, formatDoc, setColorWithCustomTemplate, setFontSizeWithTemplate, setTheme } from '@/utils'
const defaultKeyMap = CodeMirror.keyMap.default
const modPrefix
@ -34,6 +34,8 @@ export const useStore = defineStore(`store`, () => {
const output = ref(``)
// 文本字体
const theme = useStorage(addPrefix(`theme`), themeOptions[0].value)
// 文本字体
const fontFamily = useStorage(`fonts`, fontFamilyOptions[0].value)
// 文本大小
@ -45,8 +47,10 @@ export const useStore = defineStore(`store`, () => {
// 图注格式
const legend = useStorage(`legend`, legendOptions[3].value)
const fontSizeNumber = fontSize.value.replace(`px`, ``)
const wxRenderer = new WxRenderer({
theme: setColor(fontColor.value),
theme: setTheme(themeMap[theme.value], fontSizeNumber, fontColor.value, theme.value === `default`),
fonts: fontFamily.value,
size: fontSize.value,
})
@ -174,11 +178,11 @@ export const useStore = defineStore(`store`, () => {
// 更新 CSS
const updateCss = () => {
const json = css2json(cssEditor.value.getValue())
let theme = setFontSize(fontSize.value.replace(`px`, ``))
let t = setTheme(themeMap[theme.value], fontSizeNumber, fontColor.value, theme.value === `default`)
theme = customCssWithTemplate(json, fontColor.value, theme)
t = customCssWithTemplate(json, fontColor.value, t)
wxRenderer.setOptions({
theme,
theme: t,
})
editorRefresh()
}
@ -224,6 +228,8 @@ export const useStore = defineStore(`store`, () => {
isCiteStatus.value = false
isMacCodeBlock.value = true
theme.value = themeOptions[0].value
fontFamily.value = fontFamilyOptions[0].value
fontFamily.value = fontFamilyOptions[0].value
fontSize.value = fontSizeOptions[2].value
fontColor.value = colorOptions[0].value
@ -255,10 +261,18 @@ export const useStore = defineStore(`store`, () => {
}
const getTheme = (size, color) => {
const theme = setFontSize(size.replace(`px`, ``))
return setColorWithCustomTemplate(theme, color)
const t = setFontSizeWithTemplate(themeMap[theme.value])(size.replace(`px`, ``), theme.value === `default`)
return setColorWithCustomTemplate(t, color, theme.value === `default`)
}
const themeChanged = withAfterRefresh((newTheme) => {
wxRenderer.setOptions({
theme: setTheme(themeMap[newTheme], fontSizeNumber, fontColor.value, newTheme === `default`),
})
theme.value = newTheme
})
const fontChanged = withAfterRefresh((fonts) => {
wxRenderer.setOptions({
fonts,
@ -393,6 +407,7 @@ export const useStore = defineStore(`store`, () => {
output,
editor,
cssEditor,
theme,
fontFamily,
fontSize,
fontColor,
@ -401,6 +416,7 @@ export const useStore = defineStore(`store`, () => {
editorRefresh,
themeChanged,
fontChanged,
sizeChanged,
colorChanged,

View File

@ -9,13 +9,19 @@ export function addPrefix(str) {
return `${prefix}__${str}`
}
function createCustomTheme(theme, color) {
function createCustomTheme(theme, color, isDefault = true) {
const customTheme = JSON.parse(JSON.stringify(theme))
customTheme.block.h1[`border-bottom`] = `2px solid ${color}`
customTheme.block.h2.background = color
customTheme.block.h3[`border-left`] = `3px solid ${color}`
customTheme.block.h4.color = color
customTheme.inline.strong.color = color
if (!isDefault) {
customTheme.block.h3[`border-bottom`] = `1px dashed ${color}`
customTheme.block.blockquote[`border-left`] = `4px solid ${color}`
}
return customTheme
}
@ -26,24 +32,34 @@ export function setColorWithTemplate(theme) {
}
}
export function setColorWithCustomTemplate(theme, color) {
return createCustomTheme(theme, color)
export function setColorWithCustomTemplate(theme, color, isDefault = true) {
return createCustomTheme(theme, color, isDefault)
}
// 设置自定义字体大小
export function setFontSizeWithTemplate(template) {
return function (fontSize) {
return function (fontSize, isDefault = true) {
const customTheme = JSON.parse(JSON.stringify(template))
if (isDefault) {
customTheme.block.h1[`font-size`] = `${fontSize * 1.2}px`
customTheme.block.h2[`font-size`] = `${fontSize * 1.2}px`
customTheme.block.h3[`font-size`] = `${fontSize * 1.1}px`
customTheme.block.h4[`font-size`] = `${fontSize}px`
}
else {
customTheme.block.h1[`font-size`] = `${fontSize * 1.4}px`
customTheme.block.h2[`font-size`] = `${fontSize * 1.3}px`
customTheme.block.h3[`font-size`] = `${fontSize * 1.2}px`
customTheme.block.h4[`font-size`] = `${fontSize * 1.1}px`
}
return customTheme
}
}
export const setColor = setColorWithTemplate(defaultTheme)
export const setFontSize = setFontSizeWithTemplate(defaultTheme)
export function setTheme(theme, fontSize, color, isDefault) {
return setColorWithCustomTemplate(setFontSizeWithTemplate(theme)(fontSize, isDefault), color, isDefault)
}
export function customCssWithTemplate(jsonString, color, theme) {
// block