feat: 完成移动端集成CodeMirror

This commit is contained in:
Baobhan Sith
2025-06-04 12:54:38 +08:00
parent d31c468e37
commit 39808e5abb
6 changed files with 70 additions and 7 deletions
@@ -55,6 +55,9 @@ let terminalTextStrokeEnabledFound = false;
case 'editorFontSize': case 'editorFontSize':
settings.editorFontSize = parseInt(row.value, 10); settings.editorFontSize = parseInt(row.value, 10);
break; break;
case 'mobileEditorFontSize':
settings.mobileEditorFontSize = parseInt(row.value, 10);
break;
case 'terminalBackgroundImage': case 'terminalBackgroundImage':
settings.terminalBackgroundImage = row.value || undefined; settings.terminalBackgroundImage = row.value || undefined;
break; break;
@@ -122,6 +125,7 @@ case 'terminalTextStrokeEnabled':
terminalFontSize: settings.terminalFontSize ?? defaults.terminalFontSize, terminalFontSize: settings.terminalFontSize ?? defaults.terminalFontSize,
terminalFontSizeMobile: settings.terminalFontSizeMobile ?? defaults.terminalFontSizeMobile, terminalFontSizeMobile: settings.terminalFontSizeMobile ?? defaults.terminalFontSizeMobile,
editorFontSize: settings.editorFontSize ?? defaults.editorFontSize, editorFontSize: settings.editorFontSize ?? defaults.editorFontSize,
mobileEditorFontSize: settings.mobileEditorFontSize ?? defaults.mobileEditorFontSize,
editorFontFamily: settings.editorFontFamily ?? defaults.editorFontFamily, editorFontFamily: settings.editorFontFamily ?? defaults.editorFontFamily,
terminalBackgroundImage: settings.terminalBackgroundImage ?? defaults.terminalBackgroundImage, terminalBackgroundImage: settings.terminalBackgroundImage ?? defaults.terminalBackgroundImage,
pageBackgroundImage: settings.pageBackgroundImage ?? defaults.pageBackgroundImage, pageBackgroundImage: settings.pageBackgroundImage ?? defaults.pageBackgroundImage,
@@ -172,6 +176,7 @@ const getDefaultAppearanceSettings = (): Omit<AppearanceSettings, '_id'> => {
terminalFontSize: 14, terminalFontSize: 14,
terminalFontSizeMobile: 14, // 移动端默认字体大小 terminalFontSizeMobile: 14, // 移动端默认字体大小
editorFontSize: 14, editorFontSize: 14,
mobileEditorFontSize: 16, //移动端编辑器默认字体大小
editorFontFamily: 'Consolas, "Noto Sans SC", "Microsoft YaHei"', editorFontFamily: 'Consolas, "Noto Sans SC", "Microsoft YaHei"',
terminalBackgroundImage: undefined, terminalBackgroundImage: undefined,
pageBackgroundImage: undefined, pageBackgroundImage: undefined,
@@ -213,6 +218,7 @@ export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise<
{ key: 'terminalFontSize', value: defaults.terminalFontSize }, { key: 'terminalFontSize', value: defaults.terminalFontSize },
{ key: 'terminalFontSizeMobile', value: defaults.terminalFontSizeMobile }, { key: 'terminalFontSizeMobile', value: defaults.terminalFontSizeMobile },
{ key: 'editorFontSize', value: defaults.editorFontSize }, { key: 'editorFontSize', value: defaults.editorFontSize },
{ key: 'mobileEditorFontSize', value: defaults.mobileEditorFontSize },
{ key: 'editorFontFamily', value: defaults.editorFontFamily }, { key: 'editorFontFamily', value: defaults.editorFontFamily },
{ key: 'terminalBackgroundImage', value: defaults.terminalBackgroundImage ?? '' }, // 数据库中使用空字符串 { key: 'terminalBackgroundImage', value: defaults.terminalBackgroundImage ?? '' }, // 数据库中使用空字符串
{ key: 'pageBackgroundImage', value: defaults.pageBackgroundImage ?? '' }, // 数据库中使用空字符串 { key: 'pageBackgroundImage', value: defaults.pageBackgroundImage ?? '' }, // 数据库中使用空字符串
@@ -114,6 +114,16 @@ export const updateSettings = async (settingsDto: UpdateAppearanceDto): Promise<
settingsDto.editorFontSize = size; settingsDto.editorFontSize = size;
} }
// 验证 mobileEditorFontSize (如果提供了)
if (settingsDto.mobileEditorFontSize !== undefined && settingsDto.mobileEditorFontSize !== null) {
const size = Number(settingsDto.mobileEditorFontSize);
if (isNaN(size) || size <= 0) {
throw new Error(`无效的移动端编辑器字体大小: ${settingsDto.mobileEditorFontSize}。必须是一个正数。`);
}
// 确保类型正确传递给仓库层
settingsDto.mobileEditorFontSize = size;
}
// 验证 editorFontFamily (如果提供了) // 验证 editorFontFamily (如果提供了)
if (settingsDto.hasOwnProperty('editorFontFamily')) { if (settingsDto.hasOwnProperty('editorFontFamily')) {
if (settingsDto.editorFontFamily === null) { if (settingsDto.editorFontFamily === null) {
@@ -3,8 +3,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, shallowRef } from 'vue'; import { ref, onMounted, onBeforeUnmount, watch, shallowRef, computed } from 'vue';
import { EditorState, Compartment } from '@codemirror/state'; import { EditorState, Compartment } from '@codemirror/state';
import { useAppearanceStore } from '../stores/appearance.store';
import { EditorView, keymap } from '@codemirror/view'; import { EditorView, keymap } from '@codemirror/view';
import { basicSetup } from 'codemirror'; // Use basicSetup from the main 'codemirror' package import { basicSetup } from 'codemirror'; // Use basicSetup from the main 'codemirror' package
@@ -21,14 +22,18 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'request-save']); const emit = defineEmits(['update:modelValue', 'request-save']);
const appearanceStore = useAppearanceStore();
const editorRef = ref<HTMLDivElement | null>(null); const editorRef = ref<HTMLDivElement | null>(null);
const view = shallowRef<EditorView | null>(null); const view = shallowRef<EditorView | null>(null);
const languageCompartment = new Compartment(); // For dynamic language switching const languageCompartment = new Compartment(); // For dynamic language switching
// Pinch to zoom state and handlers // Pinch to zoom state and handlers
const currentFontSize = ref(16); // Initial font size in pixels // Initialize with a default, will be overwritten by store value in onMounted
const currentFontSize = ref(appearanceStore.currentMobileEditorFontSize);
const MIN_FONT_SIZE = 8; const MIN_FONT_SIZE = 8;
const MAX_FONT_SIZE = 40; const MAX_FONT_SIZE = 40;
let lastPinchDistance = 0; let lastPinchDistance = 0;
const debounceTimeout = ref<number | null>(null);
const DEBOUNCE_DELAY = 500; // 500ms 防抖延迟
const getDistance = (touches: TouchList): number => { const getDistance = (touches: TouchList): number => {
if (touches.length < 2) return 0; if (touches.length < 2) return 0;
@@ -49,6 +54,15 @@ const onTouchStart = (event: TouchEvent) => {
} }
}; };
const debouncedSetMobileEditorFontSize = (size: number) => {
if (debounceTimeout.value !== null) {
clearTimeout(debounceTimeout.value);
}
debounceTimeout.value = window.setTimeout(() => {
appearanceStore.setMobileEditorFontSize(size);
}, DEBOUNCE_DELAY);
};
const onTouchMove = (event: TouchEvent) => { const onTouchMove = (event: TouchEvent) => {
if (editorRef.value && editorRef.value.contains(event.target as Node)) { if (editorRef.value && editorRef.value.contains(event.target as Node)) {
if (event.touches.length === 2) { if (event.touches.length === 2) {
@@ -60,7 +74,9 @@ const onTouchMove = (event: TouchEvent) => {
newFontSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newFontSize)); newFontSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newFontSize));
if (Math.abs(currentFontSize.value - newFontSize) > 0.1) { // Only update if change is meaningful if (Math.abs(currentFontSize.value - newFontSize) > 0.1) { // Only update if change is meaningful
currentFontSize.value = newFontSize; currentFontSize.value = newFontSize;
// Persist the new font size to the store with debounce
debouncedSetMobileEditorFontSize(newFontSize);
} }
} }
if (newPinchDistance > 0) { if (newPinchDistance > 0) {
@@ -114,6 +130,9 @@ const getLanguageExtension = async (lang: string) => {
onMounted(async () => { onMounted(async () => {
// Initialize font size from store
currentFontSize.value = appearanceStore.currentMobileEditorFontSize;
if (editorRef.value) { if (editorRef.value) {
const langExt = await getLanguageExtension(props.language); const langExt = await getLanguageExtension(props.language);
const startState = createEditorState(props.modelValue, langExt); const startState = createEditorState(props.modelValue, langExt);
@@ -140,6 +159,10 @@ onBeforeUnmount(() => {
editorRef.value.removeEventListener('touchmove', onTouchMove); editorRef.value.removeEventListener('touchmove', onTouchMove);
editorRef.value.removeEventListener('touchend', onTouchEnd); editorRef.value.removeEventListener('touchend', onTouchEnd);
} }
// Clear debounce timeout if it exists
if (debounceTimeout.value !== null) {
clearTimeout(debounceTimeout.value);
}
}); });
watch(() => props.modelValue, (newValue) => { watch(() => props.modelValue, (newValue) => {
@@ -160,6 +183,13 @@ watch(() => props.language, async (newLanguage, oldLanguage) => {
} }
}); });
// Watch for changes from the store (e.g., if changed in settings panel)
watch(() => appearanceStore.currentMobileEditorFontSize, (newSize) => {
if (newSize !== currentFontSize.value) {
currentFontSize.value = newSize;
}
});
defineExpose({ defineExpose({
focus: () => view.value?.focus(), focus: () => view.value?.focus(),
}); });
@@ -29,7 +29,7 @@ const props = defineProps({
type: String, type: String,
default: 'Consolas, "Courier New", monospace', default: 'Consolas, "Courier New", monospace',
}, },
fontSize: { // 新增 prop fontSize: {
type: Number, type: Number,
default: 14, // 默认字体大小 default: 14, // 默认字体大小
}, },
@@ -129,6 +129,12 @@ export const useAppearanceStore = defineStore('appearance', () => {
const currentEditorFontFamily = computed<string>(() => { const currentEditorFontFamily = computed<string>(() => {
return appearanceSettings.value.editorFontFamily || 'Consolas, "Noto Sans SC", "Microsoft YaHei"'; // 提供默认值 return appearanceSettings.value.editorFontFamily || 'Consolas, "Noto Sans SC", "Microsoft YaHei"'; // 提供默认值
}); });
// 当前移动端编辑器字体大小
const currentMobileEditorFontSize = computed<number>(() => {
const size = appearanceSettings.value.mobileEditorFontSize;
return typeof size === 'number' && size > 0 ? size : 16; // 默认 16
});
// 终端背景是否启用 // 终端背景是否启用
const isTerminalBackgroundEnabled = computed<boolean>(() => { const isTerminalBackgroundEnabled = computed<boolean>(() => {
@@ -342,6 +348,14 @@ export const useAppearanceStore = defineStore('appearance', () => {
async function setEditorFontFamily(fontFamily: string) { async function setEditorFontFamily(fontFamily: string) {
await updateAppearanceSettings({ editorFontFamily: fontFamily }); await updateAppearanceSettings({ editorFontFamily: fontFamily });
} }
/**
* 设置移动端编辑器字体大小
* @param size 字体大小 (数字)
*/
async function setMobileEditorFontSize(size: number) {
await updateAppearanceSettings({ mobileEditorFontSize: size });
}
/** /**
* 设置终端背景是否启用 * 设置终端背景是否启用
@@ -890,7 +904,8 @@ export const useAppearanceStore = defineStore('appearance', () => {
terminalFontSizeDesktop, // + 用于在设置中分别显示/设置桌面端字号 terminalFontSizeDesktop, // + 用于在设置中分别显示/设置桌面端字号
terminalFontSizeMobile, // + 用于在设置中分别显示/设置移动端字号 terminalFontSizeMobile, // + 用于在设置中分别显示/设置移动端字号
currentEditorFontSize, currentEditorFontSize,
currentEditorFontFamily, // 新增 currentMobileEditorFontSize, // 移动端编辑器字号 getter
currentEditorFontFamily,
pageBackgroundImage, pageBackgroundImage,
terminalBackgroundImage, terminalBackgroundImage,
currentTerminalBackgroundOverlayOpacity, currentTerminalBackgroundOverlayOpacity,
@@ -904,7 +919,8 @@ export const useAppearanceStore = defineStore('appearance', () => {
setTerminalFontSize, // 设置桌面端字体大小 setTerminalFontSize, // 设置桌面端字体大小
setTerminalFontSizeMobile, // + 设置移动端字体大小 setTerminalFontSizeMobile, // + 设置移动端字体大小
setEditorFontSize, setEditorFontSize,
setEditorFontFamily, // 新增 setMobileEditorFontSize, // 设置移动端编辑器字号 action
setEditorFontFamily,
setTerminalBackgroundEnabled, setTerminalBackgroundEnabled,
createTerminalTheme, createTerminalTheme,
updateTerminalTheme, updateTerminalTheme,
@@ -11,7 +11,8 @@ export interface AppearanceSettings {
terminalFontSizeMobile?: number; // 移动端字体大小 terminalFontSizeMobile?: number; // 移动端字体大小
terminalBackgroundImage?: string; terminalBackgroundImage?: string;
pageBackgroundImage?: string; pageBackgroundImage?: string;
editorFontSize?: number; editorFontSize?: number; // 桌面端编辑器字号
mobileEditorFontSize?: number; // 移动端编辑器字号
editorFontFamily?: string | null; // Monaco Editor 字体偏好 editorFontFamily?: string | null; // Monaco Editor 字体偏好
terminalBackgroundEnabled?: boolean; // 终端背景是否启用 terminalBackgroundEnabled?: boolean; // 终端背景是否启用
terminalBackgroundOverlayOpacity?: number; // 终端背景蒙版透明度 (0-1) terminalBackgroundOverlayOpacity?: number; // 终端背景蒙版透明度 (0-1)