Fix/frontend review findings #3
@@ -53,8 +53,8 @@ async function createNote() {
|
|||||||
const name = rawName.endsWith('.md') ? rawName : `${rawName}.md`
|
const name = rawName.endsWith('.md') ? rawName : `${rawName}.md`
|
||||||
const file = await workspaceService.createFile('/', name, `# ${rawName}\n\n`)
|
const file = await workspaceService.createFile('/', name, `# ${rawName}\n\n`)
|
||||||
workspaceStore.addFileToTree('/', file)
|
workspaceStore.addFileToTree('/', file)
|
||||||
workspaceStore.openFile(file.path)
|
|
||||||
await editorStore.loadFile(file.path)
|
await editorStore.loadFile(file.path)
|
||||||
|
workspaceStore.openFile(file.path)
|
||||||
await router.push('/workspace')
|
await router.push('/workspace')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ watch(() => chatStore.selectedProviderId, async (providerId) => {
|
|||||||
function send() { void chatStore.sendMessage(chatStore.inputText) }
|
function send() { void chatStore.sendMessage(chatStore.inputText) }
|
||||||
|
|
||||||
async function openCitation(citation: Citation) {
|
async function openCitation(citation: Citation) {
|
||||||
workspaceStore.openFile(citation.file_path)
|
|
||||||
await editorStore.loadFile(citation.file_path)
|
await editorStore.loadFile(citation.file_path)
|
||||||
|
workspaceStore.openFile(citation.file_path)
|
||||||
editorStore.highlightBlock(citation.block_id)
|
editorStore.highlightBlock(citation.block_id)
|
||||||
await router.push('/workspace')
|
await router.push('/workspace')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ function submitSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openResult(result: SearchResult) {
|
async function openResult(result: SearchResult) {
|
||||||
workspaceStore.openFile(result.file_path)
|
|
||||||
await editorStore.loadFile(result.file_path)
|
await editorStore.loadFile(result.file_path)
|
||||||
|
workspaceStore.openFile(result.file_path)
|
||||||
editorStore.highlightBlock(result.block_id)
|
editorStore.highlightBlock(result.block_id)
|
||||||
await router.push('/workspace')
|
await router.push('/workspace')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ async function createItem() {
|
|||||||
const name = rawName.endsWith('.md') ? rawName : `${rawName}.md`
|
const name = rawName.endsWith('.md') ? rawName : `${rawName}.md`
|
||||||
const file = await workspaceService.createFile(parentPath.value, name, `# ${rawName}\n\n`)
|
const file = await workspaceService.createFile(parentPath.value, name, `# ${rawName}\n\n`)
|
||||||
workspaceStore.addFileToTree(parentPath.value, file)
|
workspaceStore.addFileToTree(parentPath.value, file)
|
||||||
workspaceStore.openFile(file.path)
|
|
||||||
await editorStore.loadFile(file.path)
|
await editorStore.loadFile(file.path)
|
||||||
|
workspaceStore.openFile(file.path)
|
||||||
await router.push('/workspace')
|
await router.push('/workspace')
|
||||||
} else {
|
} else {
|
||||||
const folder = await workspaceService.createFolder(parentPath.value, rawName)
|
const folder = await workspaceService.createFolder(parentPath.value, rawName)
|
||||||
@@ -42,8 +42,8 @@ async function createItem() {
|
|||||||
|
|
||||||
async function openNode(node: FileNode) {
|
async function openNode(node: FileNode) {
|
||||||
if (node.type === 'folder') return workspaceStore.toggleFolder(node.path)
|
if (node.type === 'folder') return workspaceStore.toggleFolder(node.path)
|
||||||
workspaceStore.openFile(node.path)
|
|
||||||
await editorStore.loadFile(node.path)
|
await editorStore.loadFile(node.path)
|
||||||
|
workspaceStore.openFile(node.path)
|
||||||
await router.push('/workspace')
|
await router.push('/workspace')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ async function deleteTarget() {
|
|||||||
const activeWasRemoved = workspaceStore.closePath(node.path)
|
const activeWasRemoved = workspaceStore.closePath(node.path)
|
||||||
workspaceStore.removeFromTree(node.path)
|
workspaceStore.removeFromTree(node.path)
|
||||||
if (activeWasRemoved) {
|
if (activeWasRemoved) {
|
||||||
|
editorStore.closeFile()
|
||||||
if (workspaceStore.activeFilePath) await editorStore.loadFile(workspaceStore.activeFilePath)
|
if (workspaceStore.activeFilePath) await editorStore.loadFile(workspaceStore.activeFilePath)
|
||||||
else editorStore.closeFile()
|
|
||||||
}
|
}
|
||||||
closeContextMenu()
|
closeContextMenu()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ onMounted(() => {
|
|||||||
// Already loaded
|
// Already loaded
|
||||||
}
|
}
|
||||||
if (!workspaceStore.activeFilePath && workspaceStore.fileTree.length === 0) {
|
if (!workspaceStore.activeFilePath && workspaceStore.fileTree.length === 0) {
|
||||||
workspaceStore.openFile('/欢迎使用知笔知己.md')
|
void editorStore.loadFile('/欢迎使用知笔知己.md').then(() => {
|
||||||
editorStore.loadFile('/欢迎使用知笔知己.md')
|
workspaceStore.openFile('/欢迎使用知笔知己.md')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,34 +34,54 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let saveTimer: ReturnType<typeof setTimeout> | null = null
|
let saveTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let pendingSave: Promise<void> | null = null
|
||||||
|
|
||||||
function scheduleAutoSave(delay = 1500) {
|
function scheduleAutoSave(delay = 1500) {
|
||||||
if (saveTimer) clearTimeout(saveTimer)
|
if (saveTimer) clearTimeout(saveTimer)
|
||||||
saveTimer = setTimeout(() => {
|
saveTimer = setTimeout(() => {
|
||||||
|
saveTimer = null
|
||||||
void save()
|
void save()
|
||||||
}, delay)
|
}, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
if (!currentFilePath.value) return
|
if (!currentFilePath.value) return
|
||||||
if (saveStatus.value === 'saving') return
|
if (pendingSave) return pendingSave
|
||||||
const targetPath = currentFilePath.value
|
const targetPath = currentFilePath.value
|
||||||
const snapshot = content.value
|
const snapshot = content.value
|
||||||
saveStatus.value = 'saving'
|
saveStatus.value = 'saving'
|
||||||
try {
|
pendingSave = (async () => {
|
||||||
await workspaceService.saveFileContent(targetPath, snapshot)
|
try {
|
||||||
if (currentFilePath.value === targetPath) {
|
await workspaceService.saveFileContent(targetPath, snapshot)
|
||||||
saveStatus.value = content.value === snapshot ? 'saved' : 'dirty'
|
if (currentFilePath.value === targetPath) {
|
||||||
lastSavedAt.value = new Date().toISOString()
|
saveStatus.value = content.value === snapshot ? 'saved' : 'dirty'
|
||||||
|
lastSavedAt.value = new Date().toISOString()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (currentFilePath.value === targetPath) saveStatus.value = 'save_failed'
|
||||||
|
} finally {
|
||||||
|
pendingSave = null
|
||||||
}
|
}
|
||||||
} catch {
|
})()
|
||||||
saveStatus.value = 'save_failed'
|
return pendingSave
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let loadVersion = 0
|
let loadVersion = 0
|
||||||
|
|
||||||
async function loadFile(filePath: string) {
|
async function loadFile(filePath: string) {
|
||||||
|
if (currentFilePath.value === filePath) return
|
||||||
|
if (saveTimer) {
|
||||||
|
clearTimeout(saveTimer)
|
||||||
|
saveTimer = null
|
||||||
|
}
|
||||||
|
if (saveStatus.value === 'conflict') {
|
||||||
|
throw new Error('当前文件存在编辑冲突,请处理后再切换文件。')
|
||||||
|
}
|
||||||
|
if (pendingSave) await pendingSave
|
||||||
|
if (saveStatus.value === 'dirty' || saveStatus.value === 'save_failed') await save()
|
||||||
|
if (saveStatus.value === 'dirty' || saveStatus.value === 'save_failed') {
|
||||||
|
throw new Error('当前文件保存失败,已阻止切换以避免内容丢失。')
|
||||||
|
}
|
||||||
const version = ++loadVersion
|
const version = ++loadVersion
|
||||||
currentFilePath.value = filePath
|
currentFilePath.value = filePath
|
||||||
saveStatus.value = 'saving'
|
saveStatus.value = 'saving'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const useThemeStore = defineStore('theme', () => {
|
|||||||
const fontEditorSize = ref(15)
|
const fontEditorSize = ref(15)
|
||||||
const fontEditorFamily = ref('system-ui')
|
const fontEditorFamily = ref('system-ui')
|
||||||
const lineHeight = ref(1.7)
|
const lineHeight = ref(1.7)
|
||||||
|
let appearanceHydrated = false
|
||||||
|
|
||||||
const currentTheme = computed(() =>
|
const currentTheme = computed(() =>
|
||||||
themes.value.find((t) => t.theme_id === currentThemeId.value) || themes.value[0]
|
themes.value.find((t) => t.theme_id === currentThemeId.value) || themes.value[0]
|
||||||
@@ -47,6 +48,8 @@ export const useThemeStore = defineStore('theme', () => {
|
|||||||
} catch { localStorage.removeItem('editor-appearance') }
|
} catch { localStorage.removeItem('editor-appearance') }
|
||||||
}
|
}
|
||||||
const saved = localStorage.getItem('theme')
|
const saved = localStorage.getItem('theme')
|
||||||
|
appearanceHydrated = true
|
||||||
|
persistAppearance()
|
||||||
if (saved && themes.value.find((t) => t.theme_id === saved)) {
|
if (saved && themes.value.find((t) => t.theme_id === saved)) {
|
||||||
applyTheme(saved)
|
applyTheme(saved)
|
||||||
return
|
return
|
||||||
@@ -72,17 +75,17 @@ export const useThemeStore = defineStore('theme', () => {
|
|||||||
|
|
||||||
watch(fontEditorSize, (v) => {
|
watch(fontEditorSize, (v) => {
|
||||||
document.documentElement.style.setProperty('--font-editor-size', `${v}px`)
|
document.documentElement.style.setProperty('--font-editor-size', `${v}px`)
|
||||||
persistAppearance()
|
if (appearanceHydrated) persistAppearance()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
watch(lineHeight, (v) => {
|
watch(lineHeight, (v) => {
|
||||||
document.documentElement.style.setProperty('--font-editor-line-height', String(v))
|
document.documentElement.style.setProperty('--font-editor-line-height', String(v))
|
||||||
persistAppearance()
|
if (appearanceHydrated) persistAppearance()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
watch(fontEditorFamily, (v) => {
|
watch(fontEditorFamily, (v) => {
|
||||||
document.documentElement.style.setProperty('--font-editor-sans', v)
|
document.documentElement.style.setProperty('--font-editor-sans', v)
|
||||||
persistAppearance()
|
if (appearanceHydrated) persistAppearance()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user