Skip to content

Commit 002f4b6

Browse files
author
‘yueli’
committed
增加代码格式化
1 parent 5f3d6e4 commit 002f4b6

File tree

3 files changed

+197
-58
lines changed

3 files changed

+197
-58
lines changed
Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
1+
<template>
2+
<div class="space-y-4">
3+
<!-- 标题栏 -->
4+
<div class="flex items-center justify-between">
5+
<label for="editor" class="text-lg font-semibold flex items-center gap-3">
6+
<div class="p-2 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-lg">
7+
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
8+
<path
9+
stroke-linecap="round"
10+
stroke-linejoin="round"
11+
stroke-width="2"
12+
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
13+
/>
14+
</svg>
15+
</div>
16+
代码内容
17+
</label>
18+
19+
<div class="flex items-center gap-2 text-sm text-gray-400">
20+
<div class="flex items-center gap-1">
21+
<div class="w-3 h-3 bg-red-500 rounded-full"></div>
22+
<div class="w-3 h-3 bg-yellow-500 rounded-full"></div>
23+
<div class="w-3 h-3 bg-green-500 rounded-full"></div>
24+
</div>
25+
</div>
26+
</div>
27+
28+
<!-- 编辑器容器 -->
29+
<div
30+
ref="editorContainer"
31+
id="editor"
32+
class="flex min-h-[300px] w-full rounded-md border border-input bg-muted px-3 py-2 text-sm ring-offset-background font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
33+
></div>
34+
</div>
35+
</template>
36+
137
<script setup lang="ts">
2-
import { ref, onMounted, onBeforeUnmount, defineEmits, watch } from 'vue'
38+
import { ref, onMounted, onBeforeUnmount, defineProps, defineEmits, watch } from 'vue'
339
import { EditorView, basicSetup } from 'codemirror'
440
import { Compartment } from '@codemirror/state'
541
import { dracula } from '@uiw/codemirror-theme-dracula'
@@ -17,9 +53,25 @@ import { rust } from '@codemirror/lang-rust'
1753
import { yaml } from '@codemirror/lang-yaml'
1854
import { go } from '@codemirror/lang-go'
1955
56+
import type { Extension } from '@codemirror/state'
57+
58+
// -------------------- Props & Emits --------------------
59+
const props = defineProps<{
60+
modelValue: string
61+
language: string
62+
}>()
63+
64+
const emits = defineEmits<{
65+
(e: 'update:modelValue', value: string): void
66+
}>()
67+
68+
// -------------------- Editor refs --------------------
2069
const editorContainer = ref<HTMLDivElement | null>(null)
70+
const editorView = ref<EditorView | null>(null)
2171
22-
import type { Extension } from '@codemirror/state'
72+
// -------------------- 动态配置 --------------------
73+
const languageConf = new Compartment()
74+
const themeConf = new Compartment()
2375
2476
type LangExtension = (() => Extension) | null
2577
@@ -29,7 +81,6 @@ const languageExtensions: Record<string, LangExtension> = {
2981
python,
3082
java,
3183
cpp,
32-
rust,
3384
c: cpp,
3485
php,
3586
ruby: cpp,
@@ -41,28 +92,21 @@ const languageExtensions: Record<string, LangExtension> = {
4192
json,
4293
yaml,
4394
markdown,
95+
rust,
4496
go,
4597
text: null,
4698
}
4799
48-
const props = defineProps<{ language: string }>()
49-
const editorView = ref<EditorView | null>(null)
50-
const languageConf = new Compartment()
51-
const themeConf = new Compartment()
52-
53-
const emits = defineEmits<{
54-
(e: 'update:modelValue', value: string): void
55-
}>()
56-
100+
// -------------------- 初始化 Editor --------------------
57101
onMounted(() => {
58102
if (!editorContainer.value) return
59103
60104
editorView.value = new EditorView({
61105
parent: editorContainer.value,
62-
doc: '',
106+
doc: props.modelValue || '',
63107
extensions: [
64108
basicSetup,
65-
languageConf.of([]),
109+
languageConf.of(languageExtensions[props.language]?.() ?? []),
66110
themeConf.of(dracula),
67111
EditorView.editorAttributes.of({ class: 'w-full h-96' }),
68112
EditorView.updateListener.of((update) => {
@@ -75,56 +119,38 @@ onMounted(() => {
75119
})
76120
})
77121
122+
// -------------------- 动态语言 --------------------
78123
function updateLanguage(lang: string) {
79124
const ext = languageExtensions[lang] ?? null
80125
editorView.value?.dispatch({
81126
effects: languageConf.reconfigure(ext ? ext() : []),
82127
})
83128
}
84129
85-
onBeforeUnmount(() => {
86-
editorView.value?.destroy()
87-
})
88-
130+
// -------------------- 监听父组件语言变化 --------------------
89131
watch(
90132
() => props.language,
91133
(lang) => {
92134
updateLanguage(lang)
93135
},
94136
)
95-
</script>
96137
97-
<template>
98-
<div class="space-y-4">
99-
<div class="flex items-center justify-between">
100-
<label for="editor" class="text-lg font-semibold flex items-center gap-3">
101-
<div class="p-2 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-lg">
102-
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
103-
<path
104-
stroke-linecap="round"
105-
stroke-linejoin="round"
106-
stroke-width="2"
107-
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
108-
/>
109-
</svg>
110-
</div>
111-
代码内容
112-
</label>
113-
114-
<div class="flex items-center gap-2 text-sm text-gray-400">
115-
<div class="flex items-center gap-1">
116-
<div class="w-3 h-3 bg-red-500 rounded-full"></div>
117-
<div class="w-3 h-3 bg-yellow-500 rounded-full"></div>
118-
<div class="w-3 h-3 bg-green-500 rounded-full"></div>
119-
</div>
120-
</div>
121-
</div>
138+
// -------------------- 监听父组件修改内容 --------------------
139+
watch(
140+
() => props.modelValue,
141+
(newVal) => {
142+
if (!editorView.value) return
143+
const currentVal = editorView.value.state.doc.toString()
144+
if (newVal !== currentVal) {
145+
editorView.value.dispatch({
146+
changes: { from: 0, to: currentVal.length, insert: newVal },
147+
})
148+
}
149+
},
150+
)
122151
123-
<!-- 编辑器容器 -->
124-
<div
125-
ref="editorContainer"
126-
id="editor"
127-
class="flex min-h-[300px] w-full rounded-md border border-input bg-muted px-3 py-2 text-sm ring-offset-background font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
128-
></div>
129-
</div>
130-
</template>
152+
// -------------------- 销毁 --------------------
153+
onBeforeUnmount(() => {
154+
editorView.value?.destroy()
155+
})
156+
</script>

frontend/src/views/NewView.vue

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
<option value="text">Text</option>
104104
<option value="javascript">JavaScript</option>
105105
<option value="typescript">TypeScript</option>
106+
<option value="json">JSON</option>
106107
<option value="python">Python</option>
107108
<option value="java">Java</option>
108109
<option value="go">Go</option>
@@ -118,7 +119,6 @@
118119
<option value="css">CSS</option>
119120
<option value="sql">SQL</option>
120121
<option value="bash">Bash</option>
121-
<option value="json">JSON</option>
122122
<option value="yaml">YAML</option>
123123
<option value="markdown">Markdown</option>
124124
</select>
@@ -161,6 +161,7 @@
161161
</label>
162162
<div class="relative">
163163
<select
164+
v-model="visibility"
164165
id="visibility"
165166
name="visibility"
166167
class="flex h-10 appearance-none w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
@@ -346,7 +347,27 @@
346347
</div>
347348

348349
<!-- Submit Buttons -->
349-
<div class="flex flex-col sm:flex-row gap-4 pt-6">
350+
<div class="flex flex-col sm:flex-row gap-4">
351+
<!-- New Format Button -->
352+
<button
353+
type="button"
354+
@click="handleFormat"
355+
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:pointer-events-none disabled:opacity-50 border border-input bg-background text-foreground hover:bg-muted h-10 px-4 py-2 flex-1 order-last sm:order-first"
356+
>
357+
<span class="inline-flex items-center gap-2">
358+
<svg class="w-4 h-4 flex-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
359+
<path
360+
stroke-linecap="round"
361+
stroke-linejoin="round"
362+
stroke-width="2"
363+
d="M4 6h16M4 10h16M4 14h16M4 18h16"
364+
></path>
365+
</svg>
366+
<span>格式化代码</span>
367+
</span>
368+
</button>
369+
370+
<!-- Original Submit Button -->
350371
<button
351372
type="submit"
352373
id="submit"
@@ -378,8 +399,25 @@ import CodeEditor from '@/components/CodeEditor.vue'
378399
import { toast } from '@/plugins/toast'
379400
import type { CreateSnippetRequest } from '@/models/snippet'
380401
402+
// 导入 Prettier 核心及其所有必要的解析器 (Parser)
403+
import { format as prettierFormat } from 'prettier/standalone'
404+
import * as parserBabel from 'prettier/parser-babel' // JS, JSX, JSON
405+
import * as parserHtml from 'prettier/parser-html'
406+
import * as parserTypescript from 'prettier/parser-typescript'
407+
import * as parserPostcss from 'prettier/parser-postcss' // CSS, Less, SCSS
408+
import * as parserYaml from 'prettier/parser-yaml' // YAML
409+
import * as parserMarkdown from 'prettier/parser-markdown' // Markdown
410+
import * as prettierPluginEstree from 'prettier/plugins/estree'
411+
412+
const visibilityMap: Record<string, number> = {
413+
public: 0,
414+
unlisted: 1,
415+
private: 2,
416+
}
417+
418+
// 过期时间映射表 (秒)
381419
const expiresMap: Record<string, number> = {
382-
'0': 0, // 永久(登录可用)
420+
'0': 0, // 永久
383421
'10min': 10 * 60,
384422
'60min': 60 * 60,
385423
'1day': 24 * 60 * 60,
@@ -397,9 +435,85 @@ const title = ref('')
397435
const language = ref('text')
398436
const description = ref('')
399437
const author = ref('匿名用户')
400-
const visibility = ref(0)
438+
const visibility = ref('public')
401439
const expiresAt = ref('10min')
402440
441+
/**
442+
* 将应用中的语言名称映射到 Prettier 内部的 parser 名称
443+
*/
444+
const getPrettierConfig = (lang: string) => {
445+
const lowerLang = lang.toLowerCase()
446+
447+
// 默认的 Prettier 格式化选项
448+
const baseOptions = {
449+
tabWidth: 2,
450+
useTabs: false,
451+
semi: true,
452+
singleQuote: true,
453+
printWidth: 100,
454+
trailingComma: 'es5' as const,
455+
}
456+
457+
const languageMap: Record<string, string> = {
458+
javascript: 'babel',
459+
typescript: 'typescript',
460+
json: 'json',
461+
html: 'html',
462+
css: 'css',
463+
less: 'less',
464+
scss: 'scss',
465+
markdown: 'markdown', // Updated
466+
yaml: 'yaml', // Updated
467+
}
468+
469+
const parser = languageMap[lowerLang]
470+
471+
// 传递导入的解析器对象作为插件列表
472+
const plugins = [
473+
parserBabel,
474+
parserHtml,
475+
parserTypescript,
476+
parserPostcss,
477+
parserYaml, // Added
478+
parserMarkdown, // Added
479+
prettierPluginEstree,
480+
]
481+
482+
return parser ? { ...baseOptions, parser, plugins } : null
483+
}
484+
485+
// 代码格式化处理函数
486+
async function handleFormat() {
487+
if (!content.value) {
488+
toast.error('代码内容不能为空', { title: '系统提示' })
489+
return
490+
}
491+
492+
const options = getPrettierConfig(language.value)
493+
494+
if (!options) {
495+
toast.info(`Prettier 暂不支持 ${language.value} 语言的格式化。`, { title: '格式化提示' })
496+
return
497+
}
498+
499+
try {
500+
// @ts-expect-error https://github.com/prettier/prettier/issues/15473
501+
const formattedContent = await prettierFormat(content.value, options)
502+
503+
if (formattedContent !== content.value) {
504+
content.value = formattedContent.trim()
505+
toast.success('代码格式化成功!', { title: '系统提示' })
506+
} else {
507+
toast.info('代码已是最新格式,无需调整。', { title: '格式化提示' })
508+
}
509+
} catch (error: any) {
510+
console.error('Prettier Formatting error:', error)
511+
toast.error(`格式化失败,请检查代码语法是否正确。错误信息: ${error.message.split('\n')[0]}`, {
512+
title: '格式化错误',
513+
})
514+
}
515+
}
516+
403517
// 表单提交
404518
async function handleSubmit(e: Event) {
405519
e.preventDefault()
@@ -418,7 +532,7 @@ async function handleSubmit(e: Event) {
418532
expiresInSeconds: expiresMap[expiresAt.value] ?? 0,
419533
tags: tagsInput.value,
420534
author: author.value || '匿名用户',
421-
visibility: Number(visibility.value) || 0,
535+
visibility: visibilityMap[visibility.value] ?? 0,
422536
}
423537
424538
try {

frontend/src/views/SnippetView.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ async function fetchSnippet(password = '') {
250250
const response = await getSnippet(id, password)
251251
snippet.value = response.snippet
252252
passwordRequired.value = false
253-
console.log(snippet.value)
254253
} catch (err: any) {
255254
console.log(err.response)
256255
if (err.response?.status === 401) {

0 commit comments

Comments
 (0)