Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions apps/web/src/components/CodemirrorEditor/CssEditor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script setup lang="ts">
import { Edit3, Plus, X } from 'lucide-vue-next'
import { exportMergedTheme } from '@md/core'
import { themeMap, themeOptions } from '@md/shared'
import { Download, Edit3, Plus, X } from 'lucide-vue-next'
import { useDisplayStore, useStore } from '@/stores'

const store = useStore()
Expand Down Expand Up @@ -102,7 +104,7 @@ function delTab() {
}

function addHandler() {
addInputVal.value = `方案${store.cssContentConfig.tabs.length + 1}`
addInputVal.value = `方案${store.cssContentConfig.tabs.length - themeOptions.length + 1}`
isOpenAddDialog.value = true
}

Expand All @@ -116,6 +118,28 @@ function tabChanged(tabName: string | number) {
tabHistory.value = [tabHistory.value[1], tabName as string]
store.tabChanged(tabName as string)
}

// 导出合并后的主题
function exportCurrentTheme() {
const currentTab = store.cssContentConfig.tabs.find(tab => tab.name === store.cssContentConfig.active)
if (!currentTab) {
toast.error(`未找到当前方案`)
return
}

const currentThemeName = currentTab.title || currentTab.name
const fontSizeNumber = Number(store.fontSize.replace(`px`, ``))

exportMergedTheme(
currentTab.content,
themeMap[store.theme],
store.primaryColor,
fontSizeNumber,
`${currentThemeName}-merged-theme`,
)

toast.success(`主题导出成功`)
}
</script>

<template>
Expand All @@ -135,7 +159,7 @@ function tabChanged(tabName: string | number) {
'fixed top-0 right-0 w-full h-full z-100 bg-background border-l shadow-lg': store.isMobile,
'animate': store.isMobile && enableAnimation,
// 桌面端样式
'border-l-2 flex-1 order-2 border-gray/50': !store.isMobile,
'border-l-2 flex-1 order-2 border-gray/50 min-w-0': !store.isMobile,
}"
:style="{
transform: store.isMobile ? (displayStore.isShowCssEditor ? 'translateX(0)' : 'translateX(100%)') : 'none',
Expand All @@ -154,33 +178,48 @@ function tabChanged(tabName: string | number) {
v-model="store.cssContentConfig.active"
@update:model-value="tabChanged"
>
<TabsList class="w-full overflow-x-auto">
<TabsList class="w-full overflow-x-auto justify-start">
<TabsTrigger
v-for="item in store.cssContentConfig.tabs"
:key="item.name"
:value="item.name"
class="flex-1"
>
{{ item.title }}
<Edit3
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
@click="rename(item.name)"
/>
<X
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
@click.self="removeHandler(item.name)"
/>
<template v-if="!item.isBuiltIn">
<Edit3
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
@click="rename(item.name)"
/>
<X
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
@click.self="removeHandler(item.name)"
/>
</template>
</TabsTrigger>
<TabsTrigger value="add">
<Plus class="h-5 w-5" />
</TabsTrigger>
</TabsList>
</Tabs>
<textarea
id="cssEditor"
type="textarea"
placeholder="Your custom css here."
/>
<!-- CSS编辑器内容区域 -->
<div class="relative flex-1 min-h-0">
<textarea
id="cssEditor"
type="textarea"
placeholder="Your custom css here."
/>

<!-- 悬浮导出按钮 -->
<Button
class="absolute bottom-4 right-4 z-10 shadow-lg hover:bg-accent cursor-pointer transition-shadow bg-background text-background-foreground border"
size="sm"
@click="exportCurrentTheme"
>
<Download class="h-4 w-4 mr-2" />
导出主题
</Button>
</div>

<!-- 新增弹窗 -->
<Dialog v-model:open="isOpenAddDialog">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/CodemirrorEditor/RightSlider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
'animate': store.isMobile && enableAnimation,
// 桌面端样式
'border-l-2 order-2 border-gray/20 bg-white transition-width duration-300 dark:bg-[#191919]': !store.isMobile,
'w-100': !store.isMobile && store.isOpenRightSlider,
'flex-1': !store.isMobile && store.isOpenRightSlider,
'w-0 border-l-0': !store.isMobile && !store.isOpenRightSlider,
}"
:style="{
Expand Down
52 changes: 49 additions & 3 deletions apps/web/src/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { initRenderer } from '@md/core'
import { generateThemeCSS, initRenderer } from '@md/core'
import {
defaultStyleConfig,
themeMap,
themeOptionsMap,
widthOptions,
} from '@md/shared/configs'
import CodeMirror from 'codemirror'
Expand Down Expand Up @@ -290,20 +291,57 @@ export const useStore = defineStore(`store`, () => {
* @deprecated 在后续版本中将会移除
*/
const cssContent = useStorage(`__css_content`, DEFAULT_CSS_CONTENT)
const cssContentConfig = useStorage(addPrefix(`css_content_config`), {
// 初始化默认配置
const defaultCssConfig = {
active: `方案1`,
tabs: [
...Object.entries(themeMap).map(([key, value]) => ({
title: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
name: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
content: generateThemeCSS(value),
isBuiltIn: true,
})),
{
title: `方案1`,
name: `方案1`,
// 兼容之前的方案
content: cssContent.value || DEFAULT_CSS_CONTENT,
isBuiltIn: false,
},
],
})
}

const cssContentConfig = useStorage(addPrefix(`css_content_config`), defaultCssConfig)
onMounted(() => {
// 清空过往历史记录
cssContent.value = ``

// 动态合并新增的内置主题
const existingBuiltInThemes = new Set(
cssContentConfig.value.tabs
.filter(tab => tab.isBuiltIn)
.map(tab => tab.name),
)

// 检查是否有新增的主题
const newThemes = Object.entries(themeMap)
.filter(([key]) => !existingBuiltInThemes.has(themeOptionsMap[key as keyof typeof themeOptionsMap].label))
.map(([key, value]) => ({
title: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
name: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
content: generateThemeCSS(value),
isBuiltIn: true,
}))

// 如果有新主题,将其插入到内置主题区域
if (newThemes.length > 0) {
// 找到第一个非内置主题的索引
const firstCustomThemeIndex = cssContentConfig.value.tabs.findIndex(tab => !tab.isBuiltIn)
const insertIndex = firstCustomThemeIndex === -1 ? cssContentConfig.value.tabs.length : firstCustomThemeIndex

// 插入新主题
cssContentConfig.value.tabs.splice(insertIndex, 0, ...newThemes)
}
})
const getCurrentTab = () =>
cssContentConfig.value.tabs.find((tab) => {
Expand All @@ -330,6 +368,7 @@ export const useStore = defineStore(`store`, () => {
name,
title: name,
content: DEFAULT_CSS_CONTENT,
isBuiltIn: false,
})
cssContentConfig.value.active = name
setCssEditorValue(DEFAULT_CSS_CONTENT)
Expand Down Expand Up @@ -489,11 +528,18 @@ export const useStore = defineStore(`store`, () => {
cssContentConfig.value = {
active: `方案 1`,
tabs: [
...Object.entries(themeMap).map(([key, value]) => ({
title: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
name: themeOptionsMap[key as keyof typeof themeOptionsMap].label,
content: generateThemeCSS(value),
isBuiltIn: true,
})),
{
title: `方案 1`,
name: `方案 1`,
// 兼容之前的方案
content: cssContent.value || DEFAULT_CSS_CONTENT,
isBuiltIn: false,
},
],
}
Expand Down
13 changes: 8 additions & 5 deletions apps/web/src/views/CodemirrorEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ onUnmounted(() => {
<ResizablePanelGroup direction="horizontal">
<ResizablePanel
:default-size="15"
:max-size="store.isOpenPostSlider ? 30 : 0"
:max-size="store.isOpenPostSlider ? 20 : 0"
:min-size="store.isOpenPostSlider ? 10 : 0"
>
<PostSlider />
Expand Down Expand Up @@ -525,16 +525,19 @@ onUnmounted(() => {
<div
id="preview"
ref="previewRef"
class="preview-wrapper w-full p-5"
class="preview-wrapper w-full p-5 flex justify-center"
>
<div
id="output-wrapper"
class="w-full"
class="w-full max-w-full"
:class="{ output_night: !backLight }"
>
<div
class="preview border-x shadow-xl"
:class="[store.isMobile ? 'w-[100%]' : store.previewWidth]"
class="preview border-x shadow-xl mx-auto"
:class="[
store.isMobile ? 'w-full' : store.previewWidth,
store.previewWidth === 'w-[375px]' ? 'max-w-full' : '',
]"
>
<section id="output" class="w-full" v-html="output" />
<div v-if="isCoping" class="loading-mask">
Expand Down
Loading