Skip to content

Commit 3feb254

Browse files
feat: add single instance mode with open in new tab setting (v1.4.5)
- Implement Wails SingleInstanceLock to prevent multiple app instances - Opening files when app is running now opens in existing window as new tab - Add 'Open files in new tab' setting (default: enabled) - Add open-file-from-instance event handling - Update changelog for v1.4.5 Amp-Thread-ID: https://ampcode.com/threads/T-019c0489-974a-7471-be74-7e5918a8cb7c Co-authored-by: Amp <amp@ampcode.com>
1 parent b140c2c commit 3feb254

13 files changed

Lines changed: 104 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to MarkViewPro will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.4.5] - 2026-01-28
9+
10+
### Added
11+
- **Single Instance Mode**: App now uses single instance lock - opening a file when app is already running opens the file in the existing window as a new tab instead of launching a new window
12+
- **Open in New Tab Setting**: New setting to control file open behavior (default: enabled)
13+
- When enabled, files opened from file explorer or command line open in new tab
14+
- Setting available in Settings modal under "Open files in new tab"
15+
16+
### Changed
17+
- Files associated with MarkViewPro now open in the existing instance instead of spawning new windows
18+
- Improved window focus behavior when opening files from external sources
19+
20+
### Technical
21+
- Implemented Wails SingleInstanceLock with `OnSecondInstanceLaunch` callback
22+
- Added `openInNewTab` setting to backend and frontend
23+
- Added `open-file-from-instance` event handling for cross-instance communication
24+
825
## [1.3.1] - 2026-01-26
926

1027
### Added
@@ -347,6 +364,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
347364
- Vite build system
348365
- Automated CI/CD pipeline with GitHub Actions
349366

367+
[1.4.5]: https://github.com/OffLine911/MarkViewPro/releases/tag/v1.4.5
350368
[1.3.1]: https://github.com/OffLine911/MarkViewPro/releases/tag/v1.3.1
351369
[1.3.0]: https://github.com/OffLine911/MarkViewPro/releases/tag/v1.3.0
352370
[1.2.4]: https://github.com/OffLine911/MarkViewPro/releases/tag/v1.2.4

frontend/package-lock.json

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

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "markview-pro",
33
"private": true,
4-
"version": "1.4.0",
4+
"version": "1.4.4",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

frontend/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
c32391d38434aaa7a8efe86567131b89
1+
2bedf6d4319a5e5b11d3929dcfedafdb

frontend/src/App.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,43 @@ export default function App() {
115115
loadInitialFile();
116116
}, [addTab]);
117117

118+
// Handle file opened from second instance (single instance lock)
119+
useEffect(() => {
120+
const handleOpenFromInstance = async (...args: unknown[]) => {
121+
const filePath = args[0] as string;
122+
if (!filePath) return;
123+
124+
// Check if openInNewTab setting is enabled
125+
if (settings.openInNewTab) {
126+
// Check if file is already open in a tab
127+
const existingTab = tabs.find(tab => tab.filePath === filePath);
128+
if (existingTab) {
129+
setActiveTabId(existingTab.id);
130+
info('File is already open');
131+
return;
132+
}
133+
134+
// Open file in new tab
135+
const result = await wails.readFileByPath(filePath);
136+
if (result) {
137+
addTab(result.name, result.path, result.content);
138+
}
139+
} else {
140+
// If not openInNewTab, the second instance shouldn't have been blocked
141+
// But since we have single instance lock, we just open in a new tab anyway
142+
const result = await wails.readFileByPath(filePath);
143+
if (result) {
144+
addTab(result.name, result.path, result.content);
145+
}
146+
}
147+
};
148+
149+
wails.onEvent('open-file-from-instance', handleOpenFromInstance);
150+
return () => {
151+
wails.offEvent('open-file-from-instance');
152+
};
153+
}, [settings.openInNewTab, tabs, addTab, setActiveTabId, info]);
154+
118155
// Load recent files on mount
119156
useEffect(() => {
120157
const loadRecentFiles = async () => {

frontend/src/components/Settings/SettingsModal.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
195195
className="w-4 h-4 text-cyan-500 bg-zinc-700 border-zinc-600 rounded focus:ring-cyan-500"
196196
/>
197197
</label>
198+
199+
<label className="flex items-center justify-between p-2.5 bg-zinc-800/50 rounded cursor-pointer hover:bg-zinc-800 transition-colors">
200+
<div className="flex flex-col">
201+
<span className="text-xs text-zinc-300">Open files in new tab</span>
202+
<span className="text-[10px] text-zinc-500">When disabled, opens files in new window</span>
203+
</div>
204+
<input
205+
type="checkbox"
206+
checked={settings.openInNewTab}
207+
onChange={(e) => updateSettings({ openInNewTab: e.target.checked })}
208+
className="w-4 h-4 text-cyan-500 bg-zinc-700 border-zinc-600 rounded focus:ring-cyan-500"
209+
/>
210+
</label>
198211
</div>
199212
</div>
200213

frontend/src/contexts/SettingsContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const defaultSettings: Settings = {
1717
previewTheme: 'github',
1818
syncScroll: true,
1919
spellCheck: false,
20+
openInNewTab: true,
2021
};
2122

2223
interface SettingsContextType {
@@ -46,6 +47,7 @@ function backendToFrontend(backend: BackendSettings): Settings {
4647
previewTheme: backend.previewTheme || 'github',
4748
syncScroll: backend.syncScroll ?? true,
4849
spellCheck: backend.spellCheck ?? false,
50+
openInNewTab: backend.openInNewTab ?? true,
4951
};
5052
}
5153

@@ -64,6 +66,7 @@ function frontendToBackend(frontend: Settings): BackendSettings {
6466
previewTheme: frontend.previewTheme,
6567
syncScroll: frontend.syncScroll,
6668
spellCheck: frontend.spellCheck,
69+
openInNewTab: frontend.openInNewTab,
6770
};
6871
}
6972

frontend/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface Settings {
1313
previewTheme: string;
1414
syncScroll: boolean;
1515
spellCheck: boolean;
16+
openInNewTab: boolean;
1617
}
1718

1819
export interface HeadingItem {

frontend/src/utils/wailsBindings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface BackendSettings {
5555
showLineNumbers: boolean;
5656
wordWrap: boolean;
5757
spellCheck: boolean;
58+
openInNewTab: boolean;
5859
}
5960

6061
export interface FileNode {

frontend/wailsjs/go/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export namespace settings {
154154
showLineNumbers: boolean;
155155
wordWrap: boolean;
156156
spellCheck: boolean;
157+
openInNewTab: boolean;
157158

158159
static createFrom(source: any = {}) {
159160
return new UserSettings(source);
@@ -174,6 +175,7 @@ export namespace settings {
174175
this.showLineNumbers = source["showLineNumbers"];
175176
this.wordWrap = source["wordWrap"];
176177
this.spellCheck = source["spellCheck"];
178+
this.openInNewTab = source["openInNewTab"];
177179
}
178180
}
179181

0 commit comments

Comments
 (0)