From 681fbf70e8708a4161e0b844980793f8a3f0080a Mon Sep 17 00:00:00 2001 From: Michael Joseph Date: Mon, 3 Nov 2025 16:26:00 -0500 Subject: [PATCH] Add auto-reload feature for unmodified files Implements automatic reloading of unmodified markdown files when they change on disk, with a green success notification that auto-dismisses after 5 seconds with a countdown timer. Changes: - Added 'autoReloadUnmodifiedFiles' preference (default: true) in new Experimental section - Created Experimental preferences UI component with beaker icon - Modified file change detection to auto-reload unmodified files - Enhanced notification system to support auto-hide with countdown timer - Green success notification shows "Document automatically reloaded from disk (5s)" - Blue confirmation prompt for modified files remains unchanged (no auto-dismiss) - Fixed pushTabNotification to properly pass autoHide properties --- .claude/settings.local.json | 35 +++++++++ src/main/preferences/schema.json | 5 ++ .../src/assets/icons/pref_experimental.svg | 1 + .../editorWithTabs/notifications.vue | 77 ++++++++++++++++++- .../src/prefComponents/experimental/index.vue | 40 ++++++++++ .../src/prefComponents/sideBar/config.js | 9 ++- src/renderer/src/router/index.js | 6 ++ src/renderer/src/store/editor.js | 27 ++++++- src/renderer/src/store/preferences.js | 2 + static/locales/en.json | 13 +++- static/preference.json | 4 +- 11 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 src/renderer/src/assets/icons/pref_experimental.svg create mode 100644 src/renderer/src/prefComponents/experimental/index.vue diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..c472967b7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,35 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run build:mac:*)", + "Bash(npm install:*)", + "Bash(npm run rebuild-native:*)", + "Bash(CXXFLAGS=\"-std=c++17\" npm run rebuild-native:*)", + "Bash(npm run build:*)", + "Bash(npx electron-builder:*)", + "Read(//Users/michaeljosephwork/git/marktext_messed_up/**)", + "Bash(gh pr list:*)", + "Bash(gh pr view:*)", + "Bash(git remote add:*)", + "Bash(git fetch:*)", + "Bash(git cherry-pick:*)", + "Bash(git checkout:*)", + "Bash(git reset:*)", + "Bash(git stash:*)", + "Bash(git log:*)", + "Bash(git merge:*)", + "Bash(git push:*)", + "WebFetch(domain:github.com)", + "Bash(gh pr create:*)", + "Bash(cat:*)", + "Bash(npx patch-package)", + "Bash(ls:*)", + "Bash(awk:*)", + "Bash(npx patch-package:*)", + "Bash(git add:*)", + "Bash(git commit:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json index 7e3df710d..50275cffa 100644 --- a/src/main/preferences/schema.json +++ b/src/main/preferences/schema.json @@ -417,5 +417,10 @@ "description": "Watcher--Whether to use polling. Polling may leads to high CPU utilization but is necessary to watch files over a network.", "type": "boolean", "default": false + }, + "autoReloadUnmodifiedFiles": { + "description": "Experimental--Automatically reload unmodified documents when changed on disk.", + "type": "boolean", + "default": true } } diff --git a/src/renderer/src/assets/icons/pref_experimental.svg b/src/renderer/src/assets/icons/pref_experimental.svg new file mode 100644 index 000000000..cd079ed8a --- /dev/null +++ b/src/renderer/src/assets/icons/pref_experimental.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/src/components/editorWithTabs/notifications.vue b/src/renderer/src/components/editorWithTabs/notifications.vue index d69e00e1c..08d6c7cab 100644 --- a/src/renderer/src/components/editorWithTabs/notifications.vue +++ b/src/renderer/src/components/editorWithTabs/notifications.vue @@ -7,6 +7,9 @@ >
{{ currentNotification.msg }} + + ({{ countdown }}s) +
@@ -28,7 +31,7 @@ diff --git a/src/renderer/src/prefComponents/sideBar/config.js b/src/renderer/src/prefComponents/sideBar/config.js index 91c5d4678..1b02eb139 100644 --- a/src/renderer/src/prefComponents/sideBar/config.js +++ b/src/renderer/src/prefComponents/sideBar/config.js @@ -5,6 +5,7 @@ import ThemeIcon from '@/assets/icons/pref_theme.svg' import ImageIcon from '@/assets/icons/pref_image.svg' import SpellIcon from '@/assets/icons/pref_spellcheck.svg' import KeyBindingIcon from '@/assets/icons/pref_key_binding.svg' +import ExperimentalIcon from '@/assets/icons/pref_experimental.svg' import preferences from '../../../../main/preferences/schema.json' import { t } from '../../i18n' @@ -51,6 +52,12 @@ export const getCategory = () => [ label: 'keybindings', icon: KeyBindingIcon, path: '/preference/keybindings' + }, + { + name: t('preferences.categories.experimental'), + label: 'experimental', + icon: ExperimentalIcon, + path: '/preference/experimental' } ] @@ -93,7 +100,7 @@ export const getTranslatedSearchContent = () => { // 计算用于路由跳转的分类(仅允许已存在的路由,否则回退到 general) let routeCategory = mappedCategory - const validRoutes = ['general', 'editor', 'markdown', 'spelling', 'theme', 'image', 'keybindings'] + const validRoutes = ['general', 'editor', 'markdown', 'spelling', 'theme', 'image', 'keybindings', 'experimental'] if (!validRoutes.includes(routeCategory)) routeCategory = 'general' // 尝试翻译分类和项目 diff --git a/src/renderer/src/router/index.js b/src/renderer/src/router/index.js index dfac35119..ff52918e6 100644 --- a/src/renderer/src/router/index.js +++ b/src/renderer/src/router/index.js @@ -7,6 +7,7 @@ import SpellChecker from '@/prefComponents/spellchecker' import Theme from '@/prefComponents/theme' import Image from '@/prefComponents/image' import Keybindings from '@/prefComponents/keybindings' +import Experimental from '@/prefComponents/experimental' const parseSettingsPage = (type) => { let pageUrl = '/preference' @@ -67,6 +68,11 @@ const routes = (type) => [ path: 'keybindings', component: Keybindings, name: 'keybindings' + }, + { + path: 'experimental', + component: Experimental, + name: 'experimental' } ] } diff --git a/src/renderer/src/store/editor.js b/src/renderer/src/store/editor.js index e9f94de14..d1e43772d 100644 --- a/src/renderer/src/store/editor.js +++ b/src/renderer/src/store/editor.js @@ -71,6 +71,9 @@ export const useEditorStore = defineStore('editor', { const style = data.style || 'info' // Whether only one notification should exist. const exclusiveType = data.exclusiveType || '' + const autoHide = data.autoHide || false + const autoHideDuration = data.autoHideDuration || 3000 + const showCountdown = data.showCountdown || false const tab = this.tabs.find((t) => t.id === tabId) if (!tab) { @@ -95,7 +98,10 @@ export const useEditorStore = defineStore('editor', { showConfirm, style, exclusiveType, - action + action, + autoHide, + autoHideDuration, + showCountdown }) }, @@ -1231,7 +1237,24 @@ export const useEditorStore = defineStore('editor', { } case 'add': case 'change': { - const { autoSave } = preferencesStore + const { autoSave, autoReloadUnmodifiedFiles } = preferencesStore + + // Check if auto-reload unmodified files is enabled + if (autoReloadUnmodifiedFiles && isSaved) { + this.loadChange(change) + this.pushTabNotification({ + tabId: id, + msg: i18n.global.t('store.editor.documentAutoReloaded'), + style: 'success', + showConfirm: false, + exclusiveType: 'file_changed', + autoHide: true, + autoHideDuration: 5000, + showCountdown: true + }) + return + } + if (autoSave) { if (autoSaveTimers.has(id)) { const timer = autoSaveTimers.get(id) diff --git a/src/renderer/src/store/preferences.js b/src/renderer/src/store/preferences.js index fbc7ad124..9485f173c 100644 --- a/src/renderer/src/store/preferences.js +++ b/src/renderer/src/store/preferences.js @@ -77,6 +77,8 @@ export const usePreferencesStore = defineStore('preferences', { watcherUsePolling: false, + autoReloadUnmodifiedFiles: true, + // -------------------------------------------------------------------------- // Edit modes of the current window (not part of persistent settings) diff --git a/static/locales/en.json b/static/locales/en.json index 43bcaae3b..19e75d5d0 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -448,7 +448,8 @@ "spelling": "Spelling", "theme": "Theme", "image": "Image", - "keybindings": "Keybindings" + "keybindings": "Keybindings", + "experimental": "Experimental" }, "general": { "title": "General", @@ -854,6 +855,13 @@ "unbind": "Unbind" } }, + "experimental": { + "title": "Experimental", + "fileHandling": { + "title": "File Handling", + "autoReloadUnmodified": "Automatically reload unmodified documents when changed on disk" + } + }, "selectFont": "Select Font", "spellchecker": { "autoDetectDescription": "Automatically detect document language", @@ -1102,7 +1110,8 @@ "exportSuccessTitle": "Exported successfully", "exportSuccessMessage": "Exported \"{name}\" successfully!", "fileRemovedOnDisk": "\"{name}\" has been removed on disk.", - "fileChangedOnDisk": "\"{name}\" has been changed on disk. Do you want to reload it?" + "fileChangedOnDisk": "\"{name}\" has been changed on disk. Do you want to reload it?", + "documentAutoReloaded": "Document automatically reloaded from disk" }, "help": { "lineEndingAssertionError": "Line ending assertion error" diff --git a/static/preference.json b/static/preference.json index 304d42d8f..62c1bb728 100644 --- a/static/preference.json +++ b/static/preference.json @@ -69,5 +69,7 @@ "searchNoIgnore": false, "searchFollowSymlinks": true, - "watcherUsePolling": false + "watcherUsePolling": false, + + "autoReloadUnmodifiedFiles": true }