Skip to content

Commit 526e5c9

Browse files
committed
feat: export merged theme css
1 parent 497662f commit 526e5c9

File tree

4 files changed

+201
-62
lines changed

4 files changed

+201
-62
lines changed

apps/web/src/components/CodemirrorEditor/CssEditor.vue

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
2-
import { themeOptions } from '@md/shared'
3-
import { Edit3, Plus, X } from 'lucide-vue-next'
2+
import { exportMergedTheme } from '@md/core'
3+
import { themeMap, themeOptions } from '@md/shared'
4+
import { Download, Edit3, Plus, X } from 'lucide-vue-next'
45
import { useDisplayStore, useStore } from '@/stores'
56
67
const store = useStore()
@@ -117,6 +118,28 @@ function tabChanged(tabName: string | number) {
117118
tabHistory.value = [tabHistory.value[1], tabName as string]
118119
store.tabChanged(tabName as string)
119120
}
121+
122+
// 导出合并后的主题
123+
function exportCurrentTheme() {
124+
const currentTab = store.cssContentConfig.tabs.find(tab => tab.name === store.cssContentConfig.active)
125+
if (!currentTab) {
126+
toast.error(`未找到当前方案`)
127+
return
128+
}
129+
130+
const currentThemeName = currentTab.title || currentTab.name
131+
const fontSizeNumber = Number(store.fontSize.replace(`px`, ``))
132+
133+
exportMergedTheme(
134+
currentTab.content,
135+
themeMap[store.theme],
136+
store.primaryColor,
137+
fontSizeNumber,
138+
`${currentThemeName}-merged-theme`,
139+
)
140+
141+
toast.success(`主题导出成功`)
142+
}
120143
</script>
121144

122145
<template>
@@ -179,11 +202,24 @@ function tabChanged(tabName: string | number) {
179202
</TabsTrigger>
180203
</TabsList>
181204
</Tabs>
182-
<textarea
183-
id="cssEditor"
184-
type="textarea"
185-
placeholder="Your custom css here."
186-
/>
205+
<!-- CSS编辑器内容区域 -->
206+
<div class="relative flex-1 min-h-0">
207+
<textarea
208+
id="cssEditor"
209+
type="textarea"
210+
placeholder="Your custom css here."
211+
/>
212+
213+
<!-- 悬浮导出按钮 -->
214+
<Button
215+
class="absolute bottom-4 right-4 z-10 shadow-lg hover:shadow-xl transition-shadow"
216+
size="sm"
217+
@click="exportCurrentTheme"
218+
>
219+
<Download class="h-4 w-4 mr-2" />
220+
导出主题
221+
</Button>
222+
</div>
187223

188224
<!-- 新增弹窗 -->
189225
<Dialog v-model:open="isOpenAddDialog">

apps/web/src/stores/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { initRenderer } from '@md/core'
1+
import { generateThemeCSS, initRenderer } from '@md/core'
22
import {
33
defaultStyleConfig,
44
themeMap,
@@ -29,7 +29,6 @@ import {
2929
sanitizeTitle,
3030
} from '@/utils'
3131
import { copyPlain } from '@/utils/clipboard'
32-
import { generateThemeCSS } from '@/utils/themeHelpers'
3332

3433
/**********************************
3534
* Post 结构接口

apps/web/src/utils/themeHelpers.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/core/src/utils/themeHelpers.ts

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { Block, ExtendedProperties, Inline, Theme } from '@md/shared/types'
2+
23
import type { PropertiesHyphen } from 'csstype'
34

5+
import { selectorComments } from '@md/shared'
6+
import { css2json, downloadFile } from '@md/shared/utils'
7+
48
/**
59
* 自定义主题
610
* @param theme - 基础主题
@@ -60,27 +64,32 @@ export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline,
6064
`blockquote`,
6165
`blockquote_note`,
6266
`blockquote_tip`,
67+
`blockquote_info`,
6368
`blockquote_important`,
6469
`blockquote_warning`,
6570
`blockquote_caution`,
6671
`blockquote_p`,
6772
`blockquote_p_note`,
6873
`blockquote_p_tip`,
74+
`blockquote_p_info`,
6975
`blockquote_p_important`,
7076
`blockquote_p_warning`,
7177
`blockquote_p_caution`,
7278
`blockquote_title`,
7379
`blockquote_title_note`,
7480
`blockquote_title_tip`,
81+
`blockquote_title_info`,
7582
`blockquote_title_important`,
7683
`blockquote_title_warning`,
7784
`blockquote_title_caution`,
7885
`image`,
7986
`ul`,
8087
`ol`,
88+
`footnotes`,
89+
`figure`,
8190
`block_katex`,
8291
]
83-
const inlineKeys: Inline[] = [`listitem`, `codespan`, `link`, `wx_link`, `strong`, `table`, `thead`, `td`, `footnote`, `figcaption`, `em`, `inline_katex`]
92+
const inlineKeys: Inline[] = [`listitem`, `codespan`, `link`, `wx_link`, `strong`, `table`, `thead`, `td`, `footnote`, `figcaption`, `em`, `inline_katex`, `markup_highlight`, `markup_underline`, `markup_wavyline`]
8493

8594
mergeProperties(newTheme.block, jsonString, blockKeys)
8695
mergeProperties(newTheme.inline, jsonString, inlineKeys)
@@ -95,3 +104,150 @@ export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline,
95104
export function getStyleString(style: ExtendedProperties): string {
96105
return Object.entries(style ?? {}).map(([key, value]) => `${key}: ${value}`).join(`; `)
97106
}
107+
108+
export function generateThemeCSS(theme: Theme): string {
109+
const cssLines: string[] = []
110+
111+
// 添加注释说明
112+
cssLines.push(`/**`)
113+
cssLines.push(` * 按 Alt/Option + Shift + F 可格式化`)
114+
cssLines.push(` * 如需使用主题色,请使用 var(--md-primary-color) 代替颜色值`)
115+
cssLines.push(` * 如:color: var(--md-primary-color);`)
116+
cssLines.push(` */`)
117+
cssLines.push(``)
118+
119+
// 生成基础样式(顶层容器)
120+
cssLines.push(`/* 顶层容器样式 */`)
121+
cssLines.push(`container {`)
122+
cssLines.push(`}`)
123+
cssLines.push(``)
124+
125+
// 生成块级元素样式
126+
Object.entries(theme.block).forEach(([selector, styles]) => {
127+
if (selector !== `container`) {
128+
const comment = selectorComments[selector as Block | Inline] || `${selector}样式`
129+
cssLines.push(`/* ${comment} */`)
130+
cssLines.push(`${selector} {`)
131+
Object.entries(styles).forEach(([property, value]) => {
132+
if (value) {
133+
cssLines.push(` ${property}: ${value};`)
134+
}
135+
})
136+
cssLines.push(`}`)
137+
cssLines.push(``)
138+
}
139+
})
140+
141+
// 生成内联元素样式
142+
Object.entries(theme.inline).forEach(([selector, styles]) => {
143+
const comment = selectorComments[selector as Block | Inline] || `${selector}样式`
144+
cssLines.push(`/* ${comment} */`)
145+
cssLines.push(`${selector} {`)
146+
Object.entries(styles).forEach(([property, value]) => {
147+
if (value) {
148+
cssLines.push(` ${property}: ${value};`)
149+
}
150+
})
151+
cssLines.push(`}`)
152+
cssLines.push(``)
153+
})
154+
155+
return cssLines.join(`\n`)
156+
}
157+
158+
/**
159+
* 导出合并后的主题CSS
160+
* @param customCSS - 用户自定义的CSS内容
161+
* @param baseTheme - 基础主题
162+
* @param primaryColor - 主色调
163+
* @param fontSize - 字体大小
164+
* @param fileName - 导出文件名
165+
*/
166+
export function exportMergedTheme(
167+
customCSS: string,
168+
baseTheme: Theme,
169+
primaryColor: string,
170+
fontSize: number,
171+
fileName: string = `merged-theme`,
172+
): void {
173+
// 将自定义CSS转换为JSON格式
174+
const customThemeJson = css2json(customCSS)
175+
176+
// 使用基础主题和自定义样式合并
177+
const customizedTheme = customizeTheme(baseTheme, {
178+
fontSize,
179+
color: primaryColor,
180+
})
181+
182+
// 使用模板合并自定义CSS
183+
const mergedTheme = customCssWithTemplate(
184+
customThemeJson,
185+
primaryColor,
186+
customizedTheme,
187+
)
188+
189+
// 生成最终的CSS内容
190+
const finalCSS = generateMergedThemeCSS(mergedTheme)
191+
192+
// 下载文件
193+
downloadFile(finalCSS, `${fileName}.css`, `text/css`)
194+
}
195+
196+
/**
197+
* 生成合并后主题的完整CSS
198+
* @param theme - 合并后的主题对象
199+
* @returns CSS字符串
200+
*/
201+
function generateMergedThemeCSS(theme: Theme): string {
202+
const cssLines: string[] = []
203+
204+
// 添加文件头注释
205+
cssLines.push(`/**`)
206+
cssLines.push(` * 导出的合并主题CSS`)
207+
cssLines.push(` * 包含内置主题和自定义样式的完整合并版本`)
208+
cssLines.push(` * 生成时间: ${new Date().toLocaleString(`zh-CN`)}`)
209+
cssLines.push(` */`)
210+
cssLines.push(``)
211+
212+
// 添加CSS变量定义
213+
cssLines.push(`:root {`)
214+
Object.entries(theme.base).forEach(([property, value]) => {
215+
cssLines.push(` ${property}: ${value};`)
216+
})
217+
cssLines.push(`}`)
218+
cssLines.push(``)
219+
220+
// 生成块级元素样式
221+
Object.entries(theme.block).forEach(([selector, styles]) => {
222+
if (Object.keys(styles).length > 0) {
223+
const comment = selectorComments[selector as Block | Inline] || `${selector}样式`
224+
cssLines.push(`/* ${comment} */`)
225+
cssLines.push(`${selector} {`)
226+
Object.entries(styles).forEach(([property, value]) => {
227+
if (value) {
228+
cssLines.push(` ${property}: ${value};`)
229+
}
230+
})
231+
cssLines.push(`}`)
232+
cssLines.push(``)
233+
}
234+
})
235+
236+
// 生成内联元素样式
237+
Object.entries(theme.inline).forEach(([selector, styles]) => {
238+
if (Object.keys(styles).length > 0) {
239+
const comment = selectorComments[selector as Block | Inline] || `${selector}样式`
240+
cssLines.push(`/* ${comment} */`)
241+
cssLines.push(`${selector} {`)
242+
Object.entries(styles).forEach(([property, value]) => {
243+
if (value) {
244+
cssLines.push(` ${property}: ${value};`)
245+
}
246+
})
247+
cssLines.push(`}`)
248+
cssLines.push(``)
249+
}
250+
})
251+
252+
return cssLines.join(`\n`)
253+
}

0 commit comments

Comments
 (0)