feat(frontend): 统一页面视觉并完善 GitHub 代码主题 #6
@@ -17,32 +17,32 @@ watch(() => props.source, async (source) => {
|
||||
<div class="markdown-content" v-html="html" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style>
|
||||
.markdown-content { white-space: normal; user-select: text; }
|
||||
.markdown-content :deep(p), .markdown-content :deep(ul), .markdown-content :deep(ol), .markdown-content :deep(pre), .markdown-content :deep(blockquote) { margin: .65em 0; }
|
||||
.markdown-content :deep(h1), .markdown-content :deep(h2), .markdown-content :deep(h3) { margin: 1em 0 .5em; line-height: var(--line-height-tight); }
|
||||
.markdown-content :deep(ul) { padding-left: 1.5em; list-style: disc; }
|
||||
.markdown-content :deep(ol) { padding-left: 1.5em; list-style: decimal; }
|
||||
.markdown-content :deep(li::marker) { color: var(--color-markdown-marker); font-weight: 700; }
|
||||
.markdown-content :deep(.shiki) { overflow: auto; margin: .85em 0; padding: 16px; border: 1px solid var(--color-code-border); border-radius: 6px; background: var(--color-code-background) !important; color: var(--color-code-text); font-family: var(--font-ui-mono); font-size: .875em; line-height: 1.45; tab-size: 4; }
|
||||
.markdown-content :deep(code) { padding: .1em .3em; border-radius: var(--radius-sm); background: var(--color-background-tertiary); font-family: var(--font-ui-mono); }
|
||||
.markdown-content :deep(.shiki code) { display: block; min-width: max-content; padding: 0; background: transparent; font: inherit; }
|
||||
.markdown-content :deep(.shiki .line) { display: block; min-height: 1.45em; }
|
||||
.markdown-content :deep(blockquote) { padding-left: 1em; border-left: 3px solid var(--color-accent-primary); color: var(--color-text-secondary); }
|
||||
.markdown-content :deep(table) { width: 100%; margin: .65em 0; border-collapse: collapse; }
|
||||
.markdown-content :deep(th), .markdown-content :deep(td) { padding: .45em .65em; border: 1px solid var(--color-markdown-grid); text-align: left; }
|
||||
.markdown-content :deep(th) { background: var(--color-markdown-table-header); font-weight: 700; }
|
||||
.markdown-content :deep(img) { max-width: 100%; }
|
||||
.markdown-content :deep(hr) { margin: 1em 0; border: 0; border-top: 1px solid var(--color-border-default); }
|
||||
:global([data-code-theme='github-light']) .markdown-content :deep(.shiki),
|
||||
:global([data-code-theme='github-light']) .markdown-content :deep(.shiki span) {
|
||||
.markdown-content p, .markdown-content ul, .markdown-content ol, .markdown-content pre, .markdown-content blockquote { margin: .65em 0; }
|
||||
.markdown-content h1, .markdown-content h2, .markdown-content h3 { margin: 1em 0 .5em; line-height: var(--line-height-tight); }
|
||||
.markdown-content ul { padding-left: 1.5em; list-style: disc; }
|
||||
.markdown-content ol { padding-left: 1.5em; list-style: decimal; }
|
||||
.markdown-content li::marker { color: var(--color-markdown-marker); font-weight: 700; }
|
||||
.markdown-content .shiki { overflow: auto; margin: .85em 0; padding: 16px; border: 1px solid var(--color-code-border); border-radius: 6px; background: var(--color-code-background) !important; color: var(--color-code-text); font-family: var(--font-ui-mono); font-size: .875em; line-height: 1.45; tab-size: 4; }
|
||||
.markdown-content code { padding: .1em .3em; border-radius: var(--radius-sm); background: var(--color-background-tertiary); font-family: var(--font-ui-mono); }
|
||||
.markdown-content .shiki code { display: block; min-width: max-content; padding: 0; background: transparent; font: inherit; }
|
||||
.markdown-content .shiki .line { display: block; min-height: 1.45em; }
|
||||
.markdown-content blockquote { padding-left: 1em; border-left: 3px solid var(--color-accent-primary); color: var(--color-text-secondary); }
|
||||
.markdown-content table { width: 100%; margin: .65em 0; border-collapse: collapse; }
|
||||
.markdown-content th, .markdown-content td { padding: .45em .65em; border: 1px solid var(--color-markdown-grid); text-align: left; }
|
||||
.markdown-content th { background: var(--color-markdown-table-header); font-weight: 700; }
|
||||
.markdown-content img { max-width: 100%; }
|
||||
.markdown-content hr { margin: 1em 0; border: 0; border-top: 1px solid var(--color-border-default); }
|
||||
[data-code-theme='github-light'] .markdown-content .shiki,
|
||||
[data-code-theme='github-light'] .markdown-content .shiki span {
|
||||
color: var(--shiki-light) !important;
|
||||
font-style: var(--shiki-light-font-style) !important;
|
||||
font-weight: var(--shiki-light-font-weight) !important;
|
||||
text-decoration: var(--shiki-light-text-decoration) !important;
|
||||
}
|
||||
:global([data-code-theme='github-dark']) .markdown-content :deep(.shiki),
|
||||
:global([data-code-theme='github-dark']) .markdown-content :deep(.shiki span) {
|
||||
[data-code-theme='github-dark'] .markdown-content .shiki,
|
||||
[data-code-theme='github-dark'] .markdown-content .shiki span {
|
||||
color: var(--shiki-dark) !important;
|
||||
font-style: var(--shiki-dark-font-style) !important;
|
||||
font-weight: var(--shiki-dark-font-weight) !important;
|
||||
|
||||
@@ -288,6 +288,6 @@ defineExpose({ getEditor: () => crepe?.editor })
|
||||
.milkdown-host :deep(.milkdown-list-item-block li .label-wrapper) { color: var(--color-markdown-marker); font-weight: 700; }
|
||||
.milkdown-host :deep(.milkdown-list-item-block li .label-wrapper svg) { fill: var(--color-markdown-marker); }
|
||||
.milkdown-host :deep(code) { font-family: var(--font-editor-mono); }
|
||||
:global([data-theme='dark']) .milkdown-host :deep(.milkdown) { color-scheme: dark; }
|
||||
:global([data-theme='dark'] .milkdown-host .milkdown) { color-scheme: dark; }
|
||||
@media (max-width: 680px) { .toolbar-select select { min-width: 46px; width: 46px; } }
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import MarkdownContent from '@/components/common/MarkdownContent.vue'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const shikiPreview = `\`\`\`typescript
|
||||
const notes = await search('本地优先')
|
||||
\`\`\``
|
||||
const codeThemeLabel = computed(() => themeStore.resolvedCodeBlockTheme === 'github-dark'
|
||||
? 'Shiki · GitHub Dark'
|
||||
: 'Shiki · GitHub Light')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,7 +22,20 @@ const themeStore = useThemeStore()
|
||||
<p class="subtle">v{{ theme.version }} · {{ theme.builtin ? '内置主题' : theme.author }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="panel preference-panel"><h2 class="panel-title">编辑器外观</h2><div class="form-grid"><div class="field"><label>字号:{{ themeStore.fontEditorSize }}px</label><input v-model.number="themeStore.fontEditorSize" type="range" min="12" max="24" /></div><div class="field"><label>行高:{{ themeStore.lineHeight }}</label><input v-model.number="themeStore.lineHeight" type="range" min="1.2" max="2.2" step="0.1" /></div><div class="field"><label>字体</label><select v-model="themeStore.fontEditorFamily" class="select"><option value="system-ui">系统字体</option><option value="serif">衬线字体</option><option value="var(--font-ui-mono)">等宽字体</option></select></div><div class="field"><label>代码块样式</label><select v-model="themeStore.codeBlockTheme" class="select"><option value="auto">跟随主题</option><option value="github-light">GitHub Light</option><option value="github-dark">GitHub Dark</option></select><small>Markdown 渲染使用对应的 Shiki GitHub 主题</small></div></div><div class="editor-preview" :style="{ fontSize: `${themeStore.fontEditorSize}px`, lineHeight: themeStore.lineHeight, fontFamily: themeStore.fontEditorFamily }"><h3>主题预览</h3><p>知识的价值不只在于保存,更在于被重新发现和使用。</p><pre class="code-preview"><code><span class="code-keyword">const</span> <span class="code-variable">notes</span> = <span class="code-keyword">await</span> <span class="code-function">search</span>(<span class="code-string">'本地优先'</span>)</code></pre></div></div>
|
||||
<div class="panel preference-panel">
|
||||
<h2 class="panel-title">编辑器外观</h2>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>字号:{{ themeStore.fontEditorSize }}px</label><input v-model.number="themeStore.fontEditorSize" type="range" min="12" max="24" /></div>
|
||||
<div class="field"><label>行高:{{ themeStore.lineHeight }}</label><input v-model.number="themeStore.lineHeight" type="range" min="1.2" max="2.2" step="0.1" /></div>
|
||||
<div class="field"><label>字体</label><select v-model="themeStore.fontEditorFamily" class="select"><option value="system-ui">系统字体</option><option value="serif">衬线字体</option><option value="var(--font-ui-mono)">等宽字体</option></select></div>
|
||||
<div class="field"><label>代码块样式</label><select v-model="themeStore.codeBlockTheme" class="select"><option value="auto">跟随主题</option><option value="github-light">GitHub Light</option><option value="github-dark">GitHub Dark</option></select><small>Markdown 渲染使用对应的 Shiki GitHub 主题</small></div>
|
||||
</div>
|
||||
<div class="editor-preview" :style="{ fontSize: `${themeStore.fontEditorSize}px`, lineHeight: themeStore.lineHeight, fontFamily: themeStore.fontEditorFamily }">
|
||||
<div class="preview-heading"><h3>主题预览</h3><span class="badge info">{{ codeThemeLabel }}</span></div>
|
||||
<p>知识的价值不只在于保存,更在于被重新发现和使用。</p>
|
||||
<MarkdownContent class="code-theme-preview" :source="shikiPreview" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -29,14 +51,7 @@ const themeStore = useThemeStore()
|
||||
.preference-panel { display: grid; gap: var(--space-xl); }
|
||||
.editor-preview { padding: var(--space-xl); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); background: var(--color-background-secondary); }
|
||||
.editor-preview p { margin: var(--space-sm) 0; }
|
||||
.preview-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); }
|
||||
.field small { color: var(--color-text-tertiary); }
|
||||
.code-preview { overflow: auto; margin-top: var(--space-md); padding: 16px; border: 1px solid var(--color-code-border); border-radius: 6px; background: var(--color-code-background); color: var(--color-code-text); font: 13px/1.45 var(--font-ui-mono); }
|
||||
:global([data-code-theme='github-light']) .code-keyword { color: #cf222e; }
|
||||
:global([data-code-theme='github-light']) .code-variable { color: #24292f; }
|
||||
:global([data-code-theme='github-light']) .code-function { color: #8250df; }
|
||||
:global([data-code-theme='github-light']) .code-string { color: #0a3069; }
|
||||
:global([data-code-theme='github-dark']) .code-keyword { color: #ff7b72; }
|
||||
:global([data-code-theme='github-dark']) .code-variable { color: #c9d1d9; }
|
||||
:global([data-code-theme='github-dark']) .code-function { color: #d2a8ff; }
|
||||
:global([data-code-theme='github-dark']) .code-string { color: #a5d6ff; }
|
||||
.code-theme-preview { margin-top: var(--space-md); }
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,7 @@ const globalStyles = [
|
||||
|
||||
const markdownStyles = [
|
||||
new URL('../features/editor/VisualMarkdownEditor.vue', import.meta.url),
|
||||
new URL('../features/themes/ThemesView.vue', import.meta.url),
|
||||
new URL('../components/common/MarkdownContent.vue', import.meta.url),
|
||||
].map((path) => readFileSync(path, 'utf8')).join('\n')
|
||||
|
||||
@@ -36,4 +37,8 @@ describe('轻量动效基线', () => {
|
||||
expect(markdownStyles).toContain('var(--color-markdown-grid)')
|
||||
expect(markdownStyles).toContain('var(--color-markdown-marker)')
|
||||
})
|
||||
|
||||
it('不混用可能丢失后代选择器的 scoped global 写法', () => {
|
||||
expect(markdownStyles).not.toMatch(/:global\([^\n]+\)\s+\./)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user