Skip to content

Commit ea4c750

Browse files
committed
feat: 支持编码切换偏好设置
1 parent 83f853e commit ea4c750

File tree

10 files changed

+63
-54
lines changed

10 files changed

+63
-54
lines changed

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"axios": "^1.13.2",
1515
"buffer": "^6.0.3",
1616
"element-plus": "^2.11.9",
17+
"iconv-lite": "^0.7.1",
1718
"lodash": "^4.17.21",
1819
"marked": "^17.0.1",
1920
"monaco-editor": "^0.54.0",

frontend/pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/MonacoEditor.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
camera.open({
2222
path: code.path,
2323
value: code.value,
24-
encode: code.encode,
2524
callback: (v) => {
2625
code.value = v
2726
user.cfg.fileCameraUseDoSave && save()
@@ -43,7 +42,7 @@
4342

4443
<el-switch v-if="code.lang === 'markdown'" v-model="mdView" inline-prompt active-text="预览" inactive-text="源码" />
4544

46-
<el-select v-model="code.lang" style="width: 120px" size="small" filterable placement="top" @change="changeLang">
45+
<el-select v-if="code.lang" v-model="code.lang" style="width: 120px" size="small" filterable placement="top" @change="changeLang">
4746
<el-option v-for="item in LANG_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
4847
</el-select>
4948

@@ -92,8 +91,9 @@ const { code, load, save } = useCode({
9291
const { editorDidMount, changeLang, changeTheme, changeOption } = useEditor({ onSave: save })
9392
9493
const changeEncode = async (v: string) => {
95-
const buffer = await code.blob.arrayBuffer()
96-
code.org = code.value = new TextDecoder(v).decode(buffer)
94+
if (code.org === code.value || user.cfg.fileEncodeFromOrg) {
95+
code.org = code.value = new TextDecoder(v).decode(code.buffer)
96+
}
9797
}
9898
9999
watch(

frontend/src/hooks/useCode.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { reactive } from 'vue'
22
import { dayjs, ElMessage, ElMessageBox } from 'element-plus'
33
import axios from 'axios'
4+
import iconv from 'iconv-lite'
45

56
import { HOST } from '@/utils/env'
67
import { LANG_MAP } from '@/utils/option'
@@ -15,8 +16,8 @@ interface OptionModel {
1516
}
1617

1718
interface CodeModel {
19+
buffer: ArrayBuffer
1820
path: string
19-
blob: Blob
2021
org: string
2122
value: string
2223
lang: string
@@ -29,8 +30,8 @@ export default function useCode(option: OptionModel) {
2930
// const open = useOpenStore()
3031

3132
const code = reactive<CodeModel>({
33+
buffer: new ArrayBuffer(),
3234
path: '',
33-
blob: new Blob(),
3435
org: '',
3536
value: '',
3637
lang: '',
@@ -49,15 +50,15 @@ export default function useCode(option: OptionModel) {
4950
responseType: 'blob',
5051
})
5152

52-
const info = await getEncodeValue(data)
53-
54-
code.blob = data
53+
code.buffer = await data.arrayBuffer()
5554
code.path = path
56-
code.encode = info.encode
57-
code.org = code.value = info.value
5855
code.byte = headers['x-size'] ? Number(headers['x-size']) : undefined
5956
code.date = headers['x-update-date'] ? dayjs(headers['x-update-date']) : undefined
6057

58+
const info = getEncodeValue(code.buffer)
59+
code.encode = info.encode
60+
code.org = code.value = info.value
61+
6162
// if (await isBinaryContent(data)) {
6263
// option.onError('不支持二进制文件的编辑')
6364
// open.removeHistory(path)
@@ -118,6 +119,7 @@ export default function useCode(option: OptionModel) {
118119
code.date = dayjs(value.data.time)
119120

120121
code.org = code.value
122+
code.buffer = iconv.encode(code.value, code.encode)
121123

122124
option.onSave()
123125
} else {

frontend/src/layout/DialogLike.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,27 @@
121121
<el-switch v-model="cfg.fileCameraUseDoSave" />
122122
</div>
123123
</div>
124+
125+
<div class="item">
126+
<div class="label">
127+
<el-popover placement="top" :width="200">
128+
<template #reference>
129+
<el-icon><Warning /></el-icon>
130+
</template>
131+
<div>未编辑时切换编码将正常切换;编辑后切换编码将不会触发,保存时以切换后的编码保存。</div>
132+
<div>启用此配置后,编辑后切换编码将忽略编辑内容直接从未编辑状态切换编码。</div>
133+
</el-popover>
134+
<div class="t">切换编码忽略编辑</div>
135+
</div>
136+
<div class="value">
137+
<el-switch v-model="cfg.fileEncodeFromOrg" />
138+
</div>
139+
</div>
124140
</el-tab-pane>
125141
<el-tab-pane label="目录" name="folder">
126142
<div class="item">
127143
<div class="label">
128-
<el-popover content="选择后,启动时将自动打开该目录" placement="top" :width="150">
144+
<el-popover content="选择后,启动时将自动打开该目录" placement="top" :width="200">
129145
<template #reference>
130146
<el-icon><Warning /></el-icon>
131147
</template>

frontend/src/layout/ViewRight/RightBox.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</template>
1515

1616
<ViewBox
17+
ref="editorRef"
1718
:value="item"
1819
@next="item.wait = false"
1920
@diff="
@@ -38,6 +39,7 @@
3839
</el-tabs>
3940

4041
<el-button
42+
v-show="editorRef[editor.index]?.isEditor()"
4143
size="small"
4244
class="save"
4345
v-bind="editor.view[editor.index]?.diff ? { type: 'primary' } : { disabled: true }"
@@ -66,7 +68,7 @@ const editor = useEditorStore()
6668
6769
const { active } = storeToRefs(editor)
6870
69-
const editorRef = ref<{ save: () => void }[]>([])
71+
const editorRef = ref<{ isEditor: () => boolean; save: () => void }[]>([])
7072
7173
watch(
7274
() => active.value,

frontend/src/layout/ViewRight/ViewBox.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ import type { ViewModel as EditorViewModel } from '@/store/editor'
3535
const $props = defineProps<{ value: EditorViewModel }>()
3636
const $emit = defineEmits<{ next: []; diff: [v: boolean] }>()
3737
38+
defineExpose({
39+
isEditor: () => !!editorRef.value,
40+
save: () => editorRef.value?.save(),
41+
})
42+
43+
const editorRef = ref<{ save: () => void }>()
44+
3845
const error = ref('')
3946
const fileType = computed(() => FILE_MAP[getFileSuffix($props.value.path)])
4047
const isBinaryFile = computed(() => fileType.value && !['img', 'pdf'].includes(fileType.value))

frontend/src/store/camera.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ interface ValueModel {
1919
interface OpenModel {
2020
path: string
2121
value: string
22-
encode: string
2322
callback?: (v: string) => void
2423
}
2524

@@ -34,7 +33,7 @@ export const useCameraStore = defineStore('camera', () => {
3433

3534
const data = ref<ValueModel[]>([])
3635

37-
const open = (opt: { path: string; value: string; encode: string; callback?: (v: string) => void }) => {
36+
const open = (opt: { path: string; value: string; callback?: (v: string) => void }) => {
3837
option.value = opt
3938
show.value = true
4039
load()
@@ -76,9 +75,9 @@ export const useCameraStore = defineStore('camera', () => {
7675
await axios.post(
7776
HOST,
7877
{
79-
encode: option.value.encode,
80-
value: option.value.value,
8178
path: filePath,
79+
value: option.value.value,
80+
encode: 'utf-8',
8281
force: 1,
8382
},
8483
{ params: { _api: 'save' } },

frontend/src/store/user.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface LikeModel {
2121
fileBigWait: number // 多大文件暂停加载,0为不启用
2222
fileCameraUseConfirm: boolean // 切换快照时询问
2323
fileCameraUseDoSave: boolean // 切换快照立即保存
24-
fileEncodeFrom: 'org' | 'value' // 切换编码从何处选择
24+
fileEncodeFromOrg: boolean // 切换编码忽略编辑
2525
editorOption: {
2626
// 编辑器配置
2727
fontSize: number // 字体大小
@@ -52,7 +52,7 @@ const getDef = (): LikeModel => ({
5252
fileBigWait: 50 * 1024 * 1024, // 多大文件暂停加载,0为不启用,默认50M
5353
fileCameraUseConfirm: true, // 切换快照时询问
5454
fileCameraUseDoSave: false, // 切换快照立即保存
55-
fileEncodeFrom: 'org', // 切换编码从何处选择
55+
fileEncodeFromOrg: false, // 切换编码忽略编辑
5656
editorOption: {
5757
// 编辑器配置
5858
fontSize: 14, // 字体大小

frontend/src/utils/file.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ export const getKey = (path: string) =>
4343
.join('-')
4444
.replace(/\./g, '_')
4545

46-
export const getEncodeValue = async (blob: Blob) => {
46+
export const getEncodeValue = (buffer: ArrayBuffer) => {
4747
const encode: string[] = []
48-
const buffer = await blob.arrayBuffer()
4948

5049
for (const item of ENCODING_OPTIONS) {
5150
try {
@@ -70,36 +69,3 @@ export const getEncodeValue = async (blob: Blob) => {
7069

7170
return { encode: encode[0] || 'utf8', value: new TextDecoder(encode[0] || 'utf8').decode(buffer) }
7271
}
73-
74-
export const isBinaryContent = async (blob: Blob) => {
75-
const slice = blob.slice(0, 1024)
76-
const arrayBuffer = await slice.arrayBuffer()
77-
const bytes = new Uint8Array(arrayBuffer)
78-
79-
let suspiciousBytes = 0
80-
const totalBytes = bytes.length
81-
82-
// 空文件视为文本文件
83-
if (totalBytes === 0) return false
84-
85-
// 检查是否有 null 字节(文本文件中罕见)
86-
if (bytes.includes(0)) return true
87-
88-
// 统计非 ASCII 字符
89-
for (let i = 0; i < totalBytes; i++) {
90-
const byte = bytes[i]
91-
92-
// ASCII 控制字符(除了常见的制表符、换行符等)
93-
if (byte && byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
94-
suspiciousBytes++
95-
}
96-
97-
// 如果超过一定比例的非文本字符,则认为是二进制
98-
if (i > 100 && suspiciousBytes / i > 0.3) {
99-
return true
100-
}
101-
}
102-
103-
// 如果有很多可疑字节,认为是二进制
104-
return suspiciousBytes / totalBytes > 0.1
105-
}

0 commit comments

Comments
 (0)