From c4c7b7fed75d57ec8f60a2e60e86d46a7b770320 Mon Sep 17 00:00:00 2001 From: YangFong Date: Mon, 19 Aug 2024 11:09:09 +0800 Subject: [PATCH] feat: support for theme options (#326) close #322 --------- Co-Authored-By: brzhang <1595819400@qq.com> --- .../EditorHeader/StyleDropdown.vue | 8 +- src/config/index.js | 268 ++++++++++++++++++ src/stores/index.js | 32 ++- src/utils/index.js | 36 ++- 4 files changed, 324 insertions(+), 20 deletions(-) diff --git a/src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue b/src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue index f387677..c6d818a 100644 --- a/src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue +++ b/src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue @@ -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() { + + - + 重置 diff --git a/src/config/index.js b/src/config/index.js index a499031..b891e59 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -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, +} diff --git a/src/stores/index.js b/src/stores/index.js index dc70f65..a05aa95 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -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, diff --git a/src/utils/index.js b/src/utils/index.js index e0dd9dd..ff45d4a 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -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)) - 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` + 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