Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions lang/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,126 @@
"en": "",
"fr": ""
},
"ccpga9z": {
"zh-cn": "文件已被外部修改,但您有未保存的更改。请先保存或丢弃更改后再重新加载。",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"d4k9sh4": {
"zh-cn": "文件 \"",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"t04it76": {
"zh-cn": "\" 已被删除",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"uje4506": {
"zh-cn": "监听文件 \"",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"egxfh7": {
"zh-cn": "\" 时出错: ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"diprg14": {
"zh-cn": "未知错误",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"6lbcrh7": {
"zh-cn": "文件已重新加载",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"r2ntmg7": {
"zh-cn": " 文件已更改 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"i8xfh8c": {
"zh-cn": "\" 已被其他程序修改。 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"cbnuzvd": {
"zh-cn": " 文件已被其他程序修改。 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"ygx5m7r": {
"zh-cn": " 是否要重新加载文件内容?当前未保存的更改将会丢失。 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"u5udsw6": {
"zh-cn": " 重新加载 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"qzrwgd7": {
"zh-cn": " 文件已变动 ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"umfap0k": {
"zh-cn": "\" 已经变动,是否覆盖当前编辑的内容? ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"fdal1pl": {
"zh-cn": " 文件已经变动,是否覆盖当前编辑的内容? ",
"ja": "",
"ko": "",
"ru": "",
"en": "",
"fr": ""
},
"aroz724": {
"zh-cn": "切换语言",
"ja": "",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@vueuse/core": "^14.0.0",
"autodialog.js": "^0.0.9",
"autotoast.js": "^3.0.3",
"chokidar": "^4.0.3",
"docx": "^9.5.1",
"font-list": "^1.5.1",
"he": "^1.2.0",
Expand Down
14 changes: 12 additions & 2 deletions src/hooks/useContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import { useUpdateDialog } from './useUpdateDialog'
export function useContext() {
// 初始化所有hooks
const { updateTitle } = useTitle()
const { markdown } = useContent()
const { markdown, originalContent } = useContent()
const { currentTheme, init: initTheme } = useTheme()
const { init: initFont, currentFont } = useFont()
const { isShowSource } = useSourceCode()
const { isDialogVisible, dialogType, fileName, tabName, showDialog, showOverwriteDialog, handleSave, handleDiscard, handleCancel, handleOverwrite } = useSaveConfirmDialog()
const { isDialogVisible, dialogType, fileName, tabName, showDialog, showOverwriteDialog, showFileChangedDialog, handleSave, handleDiscard, handleCancel, handleOverwrite } = useSaveConfirmDialog()
const { isDialogVisible: isUpdateDialogVisible, showDialog: showUpdateDialog, hideDialog: hideUpdateDialog, handleIgnore, handleLater, handleUpdate } = useUpdateDialog()
const { onSave } = useFile()
const { close, switchToTab, saveCurrentTab, activeTabId, getUnsavedTabs, shouldOffsetTabBar, currentTab } = useTab()
Expand Down Expand Up @@ -49,6 +49,10 @@ export function useContext() {

// 监听文件变化事件
emitter.on('file:Change', () => {
if (currentTab.value) {
markdown.value = currentTab.value.content
originalContent.value = currentTab.value.originalContent
}
reBuildMilkdown()
})

Expand Down Expand Up @@ -123,6 +127,12 @@ export function useContext() {
data.resolver(result)
})

// 监听文件变动确认事件
emitter.on('file:changed-confirm', async (data: { fileName: string, resolver: (choice: 'cancel' | 'overwrite') => void }) => {
const result = await showFileChangedDialog(data.fileName)
data.resolver(result)
})

// 监听tab关闭确认事件
emitter.on('tab:close-confirm', async (data: { tabId: string, tabName: string, isLastTab?: boolean }) => {
pendingCloseTab.value = data
Expand Down
11 changes: 7 additions & 4 deletions src/hooks/useFont.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ async function init() {
try {
const systemFonts = await window.electronAPI.getSystemFonts()
// Font 对象数组
fontList.value = systemFonts.map(fontName => ({
label: fontName,
value: fontName,
}))
fontList.value = systemFonts.map((fontName) => {
const name = fontName.replace(/^['"]|['"]$/g, '')
return {
label: name,
value: name,
}
})

// 应用当前字体配置到 DOM
const fontConfig = getConf('font').family
Expand Down
13 changes: 12 additions & 1 deletion src/hooks/useSaveConfirmDialog.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ref } from 'vue'

type DialogType = 'close' | 'overwrite'
type DialogType = 'close' | 'overwrite' | 'file-changed'
type CloseChoice = 'save' | 'discard' | 'cancel'
type OverwriteChoice = 'save' | 'overwrite' | 'cancel'
type FileChangedChoice = 'overwrite' | 'cancel'

export function useSaveConfirmDialog() {
const isDialogVisible = ref(false)
Expand All @@ -29,6 +30,15 @@ export function useSaveConfirmDialog() {
})
}

const showFileChangedDialog = (file: string): Promise<FileChangedChoice> => {
return new Promise((resolve) => {
dialogType.value = 'file-changed'
fileName.value = file
isDialogVisible.value = true
resolvePromise.value = resolve as (value: CloseChoice | OverwriteChoice | FileChangedChoice) => void
})
}

const handleSave = () => {
isDialogVisible.value = false
if (resolvePromise.value) {
Expand Down Expand Up @@ -68,6 +78,7 @@ export function useSaveConfirmDialog() {
tabName,
showDialog,
showOverwriteDialog,
showFileChangedDialog,
handleSave,
handleDiscard,
handleCancel,
Expand Down
81 changes: 81 additions & 0 deletions src/hooks/useTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,87 @@ const currentTab = computed(() => getCurrentTab())
// 是否偏移
const shouldOffsetTabBar = computed(() => isShowOutline.value)

// 添加新tab时不通知,只有当filePath实际变化时才通知
watch(
() => tabs.value.map(tab => tab.filePath),
(newPaths, oldPaths) => {
// 获取所有真实的filePath
const newFilePaths = newPaths.filter(Boolean) as string[]
const oldFilePaths = oldPaths?.filter(Boolean) as string[] || []

// 判断是否有新的路径,包括首次执行时从空到有路径的情况,以及untitled标签被替换时监听不到的问题
const hasNewPaths = newFilePaths.some(path => !oldFilePaths.includes(path))
// 判断是否有删除的路径
const hasRemovedPaths = oldFilePaths.some(path => !newFilePaths.includes(path))
// 判断是否有路径变化,首次执行时 oldPaths 为 undefined,oldFilePaths 为 [],如果有新路径会被 hasNewPaths 捕获
const hasPathChanges = hasNewPaths || hasRemovedPaths

if (!hasPathChanges)
return
// 通知ipc
console.log('通知ipc', newFilePaths)

window.electronAPI.watchFiles(newFilePaths)
},
{
immediate: true,
},
)

// 文件变动回调事件
window.electronAPI.on?.('file:changed', async (paths) => {
const tab = tabs.value.find(tab => tab.filePath === paths)
if (!tab)
return

if (!tab.isModified) {
const result = await window.electronAPI.readFileByPath(paths)
if (!result)
return
// 处理图片路径
const processedContent = await processImagePaths(result.content, result.filePath)
// 更新
tab.content = processedContent
tab.originalContent = result.content
tab.isModified = false

// 如果当前tab是活跃的,触发内容更新事件
if (tab.id === activeTabId.value) {
emitter.emit('file:Change')
}
} else {
// 文件已变动,触发是否覆盖
const fileName = getFileName(paths)
const choice = await new Promise<'overwrite' | 'cancel'>((resolve) => {
emitter.emit('file:changed-confirm', {
fileName,
resolver: resolve,
})
})

if (choice === 'cancel') {
return
}

// 读取新文件内容
const result = await window.electronAPI.readFileByPath(paths)
if (!result)
return

// 处理图片路径
const processedContent = await processImagePaths(result.content, result.filePath)
// 更新
tab.content = processedContent
tab.originalContent = result.content
tab.isModified = false

// 触发内容更新
if (tab.id === activeTabId.value) {
emitter.emit('file:Change')
}
}
})

function useTab() {
return {
// 状态
Expand Down
55 changes: 54 additions & 1 deletion src/main/ipcBridge.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
// ipcBridge.ts

import type { FSWatcher } from 'chokidar'
import type { Block, ExportPDFOptions } from './types'
import { execSync } from 'node:child_process'
import * as fs from 'node:fs'
import path from 'node:path'
import chokidar from 'chokidar'
import { Document, HeadingLevel, Packer, Paragraph, TextRun } from 'docx'
import { app, clipboard, dialog, ipcMain, shell } from 'electron'
import { app, BrowserWindow, clipboard, dialog, ipcMain, shell } from 'electron'
import { getFonts } from 'font-list'
import { createThemeEditorWindow } from './index'

let isSaved = true
let isQuitting = false

// 存储已监听的文件路径和对应的 watcher
const watchedFiles = new Set<string>()
let watcher: FSWatcher | null = null

// 所有 on 类型监听
export function registerIpcOnHandlers(win: Electron.BrowserWindow) {
ipcMain.on('set-title', (_event, filePath: string | null) => {
Expand Down Expand Up @@ -608,6 +614,53 @@ export function registerGlobalIpcHandlers() {
return []
}
})

// 监听文件变化
ipcMain.on('file:watch', (_event, filePaths: string[]) => {
console.log('监听文件变化:', filePaths)

// 先差异对比
const newFiles = filePaths.filter(filePath => !watchedFiles.has(filePath))
const removedFiles = Array.from(watchedFiles).filter(filePath => !filePaths.includes(filePath))

// 如果 watcher 不存在,创建它并设置事件监听
if (!watcher) {
watcher = chokidar.watch([], {
ignored: (path, stats) => {
// 确保总是返回 boolean 类型
if (!stats)
return false
return stats.isFile() && !path.endsWith('.md')
},
persistent: true,
})

// 设置文件变化监听
watcher.on('change', (filePath) => {
console.log('文件变化:', filePath)
console.log(123123123123123)

const mainWindow = BrowserWindow.getAllWindows()[0]
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('file:changed', filePath)
}
})
}

// 新增的文件 - 添加到 watcher
if (newFiles.length > 0) {
watcher.add(newFiles)
newFiles.forEach(filePath => watchedFiles.add(filePath))
console.log('➕:', newFiles)
}

// 移除的文件 - 从 watcher 中移除
if (removedFiles.length > 0) {
watcher.unwatch(removedFiles)
removedFiles.forEach(filePath => watchedFiles.delete(filePath))
console.log('➖:', removedFiles)
}
})
}
export function close(win: Electron.BrowserWindow) {
// 防止重复调用
Expand Down
Loading