@happy-nut/monacori 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon.icns +0 -0
- package/dist/app-main.js +441 -158
- package/dist/assets.js +8 -1
- package/dist/build.d.ts +1 -0
- package/dist/build.js +13 -7
- package/dist/diff.d.ts +2 -1
- package/dist/diff.js +3 -3
- package/dist/i18n.js +56 -8
- package/dist/preload.cjs +15 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.js +154 -29
- package/dist/util.d.ts +5 -0
- package/dist/util.js +21 -0
- package/dist/viewer.client.js +582 -153
- package/dist/viewer.client.min.js +1 -0
- package/dist/viewer.css +202 -72
- package/package.json +10 -2
- package/scripts/patch-electron-name.mjs +23 -14
package/dist/render.js
CHANGED
|
@@ -43,6 +43,89 @@ export function renderNotGitRepoHtml(root) {
|
|
|
43
43
|
"</html>",
|
|
44
44
|
].join("\n");
|
|
45
45
|
}
|
|
46
|
+
// Welcome screen for the packaged .app (double-clicked, no cwd): an "Open Folder" button that asks the main
|
|
47
|
+
// process (window.monacoriApp.openFolder, exposed via preload) to pick a git repo and load its review.
|
|
48
|
+
export function renderWelcomeHtml(light = false, recent = []) {
|
|
49
|
+
const bg = light ? "#ffffff" : "#2b2b2b";
|
|
50
|
+
const fg = light ? "#1f2328" : "#a9b7c6";
|
|
51
|
+
// Recent projects (IntelliJ-style): one click reopens a previously reviewed repo. Each row carries its
|
|
52
|
+
// absolute path in data-path; the click handler hands it to monacoriApp.openRecent.
|
|
53
|
+
const recentItems = recent
|
|
54
|
+
.map((p) => `<button class="recent" type="button" data-path="${escapeAttr(p.path)}">` +
|
|
55
|
+
`<span class="recent-name">${escapeHtml(p.name)}</span>` +
|
|
56
|
+
`<span class="recent-path">${escapeHtml(p.path)}</span>` +
|
|
57
|
+
"</button>")
|
|
58
|
+
.join("");
|
|
59
|
+
const recentsBlock = recent.length
|
|
60
|
+
? `<div class="recents" id="recents"><div class="recents-title">Recent projects</div>${recentItems}</div>`
|
|
61
|
+
: "";
|
|
62
|
+
return [
|
|
63
|
+
"<!doctype html>",
|
|
64
|
+
'<html lang="en">',
|
|
65
|
+
"<head>",
|
|
66
|
+
'<meta charset="utf-8">',
|
|
67
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
68
|
+
"<title>Monacori</title>",
|
|
69
|
+
"<style>",
|
|
70
|
+
"* { box-sizing: border-box; }",
|
|
71
|
+
`body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: ${bg}; color: ${fg}; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }`,
|
|
72
|
+
".card { width: 520px; max-width: calc(100vw - 48px); padding: 40px; text-align: center; }",
|
|
73
|
+
".card .badge { font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: #808080; }",
|
|
74
|
+
".card h1 { font-size: 24px; margin: 12px 0 14px; color: #4a88c7; }",
|
|
75
|
+
".card p { font-size: 14px; line-height: 1.7; margin: 10px 0; }",
|
|
76
|
+
".open-btn { margin-top: 22px; padding: 10px 24px; font-size: 14px; font-weight: 600; color: #fff; background: #4a88c7; border: 0; border-radius: 8px; cursor: pointer; }",
|
|
77
|
+
".open-btn:hover { background: #3f78b3; }",
|
|
78
|
+
".open-btn:disabled { opacity: 0.6; cursor: default; }",
|
|
79
|
+
".hint { color: #d36c6c; font-size: 12px; min-height: 16px; margin-top: 16px; }",
|
|
80
|
+
// Recent projects list: left-aligned rows below the Open Folder button.
|
|
81
|
+
".recents { margin-top: 28px; text-align: left; }",
|
|
82
|
+
".recents-title { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #808080; margin: 0 0 8px; padding: 0 4px; }",
|
|
83
|
+
".recent { display: block; width: 100%; text-align: left; padding: 9px 12px; border: 0; border-radius: 8px; background: transparent; color: inherit; cursor: pointer; font: inherit; }",
|
|
84
|
+
".recent:hover { background: rgba(74, 136, 199, 0.16); }",
|
|
85
|
+
".recent:disabled { opacity: 0.5; cursor: default; }",
|
|
86
|
+
".recent-name { display: block; font-size: 13px; font-weight: 600; }",
|
|
87
|
+
".recent-path { display: block; font-size: 11px; color: #808080; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
88
|
+
"</style>",
|
|
89
|
+
"</head>",
|
|
90
|
+
"<body>",
|
|
91
|
+
'<div class="card">',
|
|
92
|
+
'<div class="badge">monacori</div>',
|
|
93
|
+
"<h1>Review a Git repository</h1>",
|
|
94
|
+
"<p>Pick a folder under Git version control to review its changes.</p>",
|
|
95
|
+
'<button class="open-btn" id="open" type="button">Open Folder…</button>',
|
|
96
|
+
'<p class="hint" id="hint"></p>',
|
|
97
|
+
recentsBlock,
|
|
98
|
+
"</div>",
|
|
99
|
+
"<script>",
|
|
100
|
+
"var btn = document.getElementById('open'), hint = document.getElementById('hint');",
|
|
101
|
+
"btn.addEventListener('click', function () {",
|
|
102
|
+
" if (!(window.monacoriApp && window.monacoriApp.openFolder)) { hint.textContent = 'Open Folder is unavailable.'; return; }",
|
|
103
|
+
" btn.disabled = true; hint.textContent = '';",
|
|
104
|
+
" window.monacoriApp.openFolder().then(function (r) {",
|
|
105
|
+
" btn.disabled = false;",
|
|
106
|
+
" if (r && r.ok) return;",
|
|
107
|
+
" if (r && r.error === 'not-git') hint.textContent = 'That folder is not a Git repository.';",
|
|
108
|
+
" }).catch(function () { btn.disabled = false; });",
|
|
109
|
+
"});",
|
|
110
|
+
// Recent Projects: click a row to reopen that repo in this window. A removed/non-git folder drops out.
|
|
111
|
+
"var recents = document.getElementById('recents');",
|
|
112
|
+
"if (recents) recents.addEventListener('click', function (e) {",
|
|
113
|
+
" var item = e.target.closest ? e.target.closest('.recent') : null;",
|
|
114
|
+
" if (!item) return;",
|
|
115
|
+
" var path = item.getAttribute('data-path');",
|
|
116
|
+
" if (!path || !(window.monacoriApp && window.monacoriApp.openRecent)) return;",
|
|
117
|
+
" item.disabled = true; hint.textContent = '';",
|
|
118
|
+
" window.monacoriApp.openRecent(path).then(function (r) {",
|
|
119
|
+
" if (r && r.ok) return;",
|
|
120
|
+
" item.disabled = false;",
|
|
121
|
+
" if (r && r.error === 'missing') { item.remove(); hint.textContent = 'That project folder is no longer available.'; }",
|
|
122
|
+
" }).catch(function () { item.disabled = false; });",
|
|
123
|
+
"});",
|
|
124
|
+
"</script>",
|
|
125
|
+
"</body>",
|
|
126
|
+
"</html>",
|
|
127
|
+
].join("\n");
|
|
128
|
+
}
|
|
46
129
|
// Above a size threshold the diff is rendered "lazily": each file's heavy body
|
|
47
130
|
// (the side-by-side tables — hundreds of thousands of rows on big repos) is moved
|
|
48
131
|
// out of the live DOM into an inert <script type="text/html"> island, leaving only
|
|
@@ -98,6 +181,32 @@ export function renderDiffHtml(input) {
|
|
|
98
181
|
const fileNav = renderDiffTree(input.files);
|
|
99
182
|
const sourceNav = renderSourceTree(input.sourceFiles);
|
|
100
183
|
const embeddedFiles = input.sourceFiles.filter((file) => file.embedded).length;
|
|
184
|
+
// IntelliJ-style activity rail: an icon per view; click navigates, hover shows a tooltip with the
|
|
185
|
+
// shortcut. data-view drives both the click handler and the active-state highlight (see syncRail).
|
|
186
|
+
const railButton = (view, labelKey, defaultLabel, kbd, svg) => `<button type="button" class="rail-btn" data-view="${view}" data-i18n-aria="${labelKey}" aria-label="${escapeAttr(defaultLabel)}">` +
|
|
187
|
+
`<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${svg}</svg>` +
|
|
188
|
+
`<span class="rail-tip"><span data-i18n="${labelKey}">${escapeHtml(defaultLabel)}</span><kbd>${escapeHtml(kbd)}</kbd></span>` +
|
|
189
|
+
"</button>";
|
|
190
|
+
const activityRail = [
|
|
191
|
+
'<nav class="activity-rail" aria-label="Views">',
|
|
192
|
+
'<div class="rail-group">',
|
|
193
|
+
railButton("changes", "tab.changes", "Changes", "⌘0", '<circle cx="12" cy="12" r="3.2"/><line x1="3.5" y1="12" x2="8.8" y2="12"/><line x1="15.2" y1="12" x2="20.5" y2="12"/>'),
|
|
194
|
+
railButton("files", "tab.files", "Files", "⌘1", '<path d="M4 7.5C4 6.7 4.7 6 5.5 6h3.2c.5 0 .9.2 1.2.6L11 8h7.3c.8 0 1.5.7 1.5 1.5v8c0 .8-.7 1.5-1.5 1.5h-13C4.7 19 4 18.3 4 17.5z"/>'),
|
|
195
|
+
railButton("q", "rail.questions", "Questions", "⌘⇧/", '<path d="M5.5 5.5h13c.8 0 1.5.7 1.5 1.5v6.4c0 .8-.7 1.5-1.5 1.5H12l-4.5 3.6V16.4H5.5c-.8 0-1.5-.7-1.5-1.5V7c0-.8.7-1.5 1.5-1.5z"/><text x="12" y="13" text-anchor="middle" font-size="9.5" font-weight="700" fill="currentColor" stroke="none">?</text>'),
|
|
196
|
+
railButton("c", "rail.changeRequests", "Change requests", "⌘⇧.", '<path d="M14.5 5.5l4 4"/><path d="M4.5 19.5l1-4 10-10 3 3-10 10z"/>'),
|
|
197
|
+
railButton("memo", "memo.title", "Prompt memo", "⌘⇧N", '<rect x="5.5" y="4" width="13" height="16" rx="1.5"/><line x1="8.5" y1="9" x2="15.5" y2="9"/><line x1="8.5" y1="12.5" x2="15.5" y2="12.5"/><line x1="8.5" y1="16" x2="12.5" y2="16"/>'),
|
|
198
|
+
"</div>",
|
|
199
|
+
'<div class="rail-group rail-bottom">',
|
|
200
|
+
// Terminal (Electron only; #terminal-toggle stays hidden until a pty exists). Same id → the existing
|
|
201
|
+
// toggle handler + is-active sync in dock-terminal.js bind to it unchanged.
|
|
202
|
+
input.app
|
|
203
|
+
? '<button type="button" id="terminal-toggle" class="rail-btn terminal-toggle hidden" data-i18n-aria="terminal.title" aria-label="Terminal"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 7l4 5-4 5"/><path d="M13 17h6"/></svg><span class="rail-tip"><span data-i18n="terminal.title">Terminal</span><kbd>⌃`</kbd></span></button>'
|
|
204
|
+
: "",
|
|
205
|
+
// Settings gear (#app-info-btn) — existing click handler binds by id.
|
|
206
|
+
'<button type="button" id="app-info-btn" class="rail-btn" aria-haspopup="dialog" data-i18n-aria="settings.title" aria-label="Settings"><span class="rail-gear" aria-hidden="true">⚙</span><span class="rail-tip"><span data-i18n="settings.title">Settings</span><kbd>⌘,</kbd></span></button>',
|
|
207
|
+
"</div>",
|
|
208
|
+
"</nav>",
|
|
209
|
+
].join("");
|
|
101
210
|
return [
|
|
102
211
|
"<!doctype html>",
|
|
103
212
|
'<html lang="en">',
|
|
@@ -115,12 +224,13 @@ export function renderDiffHtml(input) {
|
|
|
115
224
|
"<body>",
|
|
116
225
|
// Boot overlay (removed by the renderer once bootstrap has painted) covers the blank gap after loadFile.
|
|
117
226
|
'<div id="boot-overlay"><div class="boot-spinner"></div><div>monacori</div></div>',
|
|
227
|
+
activityRail,
|
|
118
228
|
'<aside class="sidebar" aria-label="Review navigation">',
|
|
119
229
|
'<div class="sidebar-scroll">',
|
|
120
|
-
`<div class="sidebar-brand" title="${escapeAttr(input.projectPath)}"><span class="brand-mark">monacori</span><span class="brand-project">${escapeHtml(input.projectName)}</span></div>`,
|
|
230
|
+
`<div class="sidebar-brand" title="${escapeAttr(input.projectPath)}"><span class="brand-mark">monacori</span><span class="brand-project">${escapeHtml(input.projectName)}</span><span class="brand-branch${input.branch ? "" : " hidden"}" data-i18n-title="rail.branch" title="Current branch"><svg class="brand-branch-icon" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6.5" cy="6" r="2.2"/><circle cx="6.5" cy="18" r="2.2"/><circle cx="17.5" cy="8.5" r="2.2"/><path d="M6.5 8.2v7.6"/><path d="M17.5 10.7c0 3.2-2.2 4.4-5.5 4.9"/></svg><span class="brand-branch-name" id="brand-branch-name">${escapeHtml(input.branch || "")}</span></span></div>`,
|
|
121
231
|
input.lazy
|
|
122
|
-
? '<div class="tabs"><button type="button" class="tab active" data-tab="changes" data-i18n="tab.changes">Changes</button><button type="button" class="tab" data-tab="files" data-i18n="tab.files">Files</button></div>'
|
|
123
|
-
: '<div class="tabs"><button type="button" class="tab" data-tab="changes" data-i18n="tab.changes">Changes</button><button type="button" class="tab active" data-tab="files" data-i18n="tab.files">Files</button></div>',
|
|
232
|
+
? '<div class="tabs"><button type="button" class="tab active" data-tab="changes" data-i18n="tab.changes" data-i18n-title="tab.changes.title" title="Changes (⌘0)">Changes</button><button type="button" class="tab" data-tab="files" data-i18n="tab.files" data-i18n-title="tab.files.title" title="Files (⌘1)">Files</button></div>'
|
|
233
|
+
: '<div class="tabs"><button type="button" class="tab" data-tab="changes" data-i18n="tab.changes" data-i18n-title="tab.changes.title" title="Changes (⌘0)">Changes</button><button type="button" class="tab active" data-tab="files" data-i18n="tab.files" data-i18n-title="tab.files.title" title="Files (⌘1)">Files</button></div>',
|
|
124
234
|
`<div class="tab-panel${input.lazy ? "" : " hidden"}" id="changes-panel">${fileNav}</div>`,
|
|
125
235
|
// Big repos: defer the (potentially huge) source tree — ship it as an inert island, materialized on
|
|
126
236
|
// the first Files-tab open, so it never builds/lays-out at startup. Small repos render it inline.
|
|
@@ -128,7 +238,7 @@ export function renderDiffHtml(input) {
|
|
|
128
238
|
? `<div class="tab-panel hidden" id="files-panel"></div><script type="text/html" id="files-tree-html">${sourceNav}</script>`
|
|
129
239
|
: `<div class="tab-panel" id="files-panel">${sourceNav}</div>`,
|
|
130
240
|
"</div>",
|
|
131
|
-
`<div class="sidebar-footer"><span class="app-version">monacori${packageVersion ? " v" + escapeHtml(packageVersion) : ""}</span><span id="app-update-flag" class="app-update-flag hidden" data-i18n="sidebar.updateAvailable" data-i18n-title="settings.updateAvailable" title="Update available">update available</span
|
|
241
|
+
`<div class="sidebar-footer"><span class="app-version">monacori${packageVersion ? " v" + escapeHtml(packageVersion) : ""}</span><span id="app-update-flag" class="app-update-flag hidden" data-i18n="sidebar.updateAvailable" data-i18n-title="settings.updateAvailable" title="Update available">update available</span></div>`,
|
|
132
242
|
'<div id="footer-progress" class="footer-progress hidden" aria-hidden="true"><div class="footer-progress-bar"></div></div>',
|
|
133
243
|
"</aside>",
|
|
134
244
|
'<div class="sidebar-resizer" aria-hidden="true"></div>',
|
|
@@ -147,7 +257,7 @@ export function renderDiffHtml(input) {
|
|
|
147
257
|
'<div class="source-file-meta"><span id="source-type-icon" class="source-type-icon" aria-hidden="true"></span><span id="source-title" data-i18n="source.title">Source</span><span id="source-meta" data-i18n="source.selectFile">Select a file from the Files tab.</span></div>',
|
|
148
258
|
'<select id="http-env-select" class="http-env-select hidden" data-i18n-title="http.env.title" data-i18n-aria="http.env.aria" title="HTTP Client environment" aria-label="HTTP environment"></select>',
|
|
149
259
|
'<button type="button" id="render-toggle" class="plain-button hidden" aria-pressed="false">Raw</button>',
|
|
150
|
-
'<button type="button" id="back-to-diff" class="plain-button" data-i18n="btn.diff">Diff</button>',
|
|
260
|
+
'<button type="button" id="back-to-diff" class="plain-button" data-i18n="btn.diff" data-i18n-title="btn.diff.title" title="Back to diff (F7)">Diff</button>',
|
|
151
261
|
"</div>",
|
|
152
262
|
'<div id="source-body" class="source-body empty" data-i18n="source.selectFile">Select a file from the Files tab.</div>',
|
|
153
263
|
"</section>",
|
|
@@ -159,7 +269,7 @@ export function renderDiffHtml(input) {
|
|
|
159
269
|
: "",
|
|
160
270
|
'<div id="quick-open" class="quick-open hidden" role="dialog" aria-modal="true" data-i18n-aria="quickopen.aria" aria-label="Quick open">',
|
|
161
271
|
'<div class="quick-open-panel">',
|
|
162
|
-
'<div class="quick-open-title"><span id="quick-open-mode" data-i18n="quickopen.searchFiles">Search files</span></div>',
|
|
272
|
+
'<div class="quick-open-title"><span id="quick-open-mode" data-i18n="quickopen.searchFiles">Search files</span><span id="quick-open-filter" class="quick-open-filter"></span></div>',
|
|
163
273
|
'<input id="quick-open-input" type="search" autocomplete="off" spellcheck="false" data-i18n-ph="quickopen.searchFiles" placeholder="Search files">',
|
|
164
274
|
'<div id="quick-open-results" class="quick-open-results"></div>',
|
|
165
275
|
'<div id="quick-open-preview" class="quick-open-preview"></div>',
|
|
@@ -183,48 +293,63 @@ export function renderDiffHtml(input) {
|
|
|
183
293
|
'<button type="button" id="settings-language" class="settings-select mc-select" data-i18n-aria="settings.language"></button>',
|
|
184
294
|
'<label class="settings-label" for="settings-theme" data-i18n="settings.theme">Theme</label>',
|
|
185
295
|
'<button type="button" id="settings-theme" class="settings-select mc-select" data-i18n-aria="settings.theme"></button>',
|
|
296
|
+
'<label class="settings-check"><input type="checkbox" id="set-bell-notify"><span data-i18n="settings.bellNotify">Notify when a terminal task finishes (bell)</span></label>',
|
|
186
297
|
'<div class="app-info-keys">' +
|
|
187
298
|
'<div class="app-info-keys-h" data-i18n="settings.kbd.title">Keyboard shortcuts</div>' +
|
|
299
|
+
'<div class="keys-cat" data-i18n="settings.kbd.cat.app">App</div>' +
|
|
300
|
+
'<div class="keys-grid">' +
|
|
301
|
+
'<kbd>⌘O</kbd><span data-i18n="kbd.openFolder">Open folder</span>' +
|
|
302
|
+
'<kbd>⌘⇧O</kbd><span data-i18n="kbd.openNewWindow">Open in new window</span>' +
|
|
303
|
+
'<kbd>⌘,</kbd><span data-i18n="kbd.openSettings">Settings</span>' +
|
|
304
|
+
'<kbd>Esc</kbd><span data-i18n="kbd.closeDialog">Close dialog / cancel</span>' +
|
|
305
|
+
'</div>' +
|
|
188
306
|
'<div class="keys-cat" data-i18n="settings.kbd.cat.nav">Navigation</div>' +
|
|
189
307
|
'<div class="keys-grid">' +
|
|
190
308
|
'<kbd>F7</kbd><span data-i18n="kbd.nextChange">Next change</span>' +
|
|
191
|
-
'<kbd
|
|
192
|
-
'<kbd
|
|
309
|
+
'<kbd>⇧F7</kbd><span data-i18n="kbd.prevChange">Previous change</span>' +
|
|
310
|
+
'<kbd>⌘1 / ⌘0</kbd><span data-i18n="kbd.filesChangesTab">Files / Changes tab</span>' +
|
|
193
311
|
'<kbd>Tab</kbd><span data-i18n="kbd.sidebarContent">Sidebar ↔ content</span>' +
|
|
194
|
-
'<kbd
|
|
195
|
-
'<kbd
|
|
196
|
-
'<kbd
|
|
197
|
-
'<kbd
|
|
198
|
-
'<kbd
|
|
199
|
-
'<kbd
|
|
200
|
-
'<kbd
|
|
201
|
-
'<kbd
|
|
202
|
-
'<kbd
|
|
203
|
-
'<kbd
|
|
204
|
-
'<kbd>
|
|
312
|
+
'<kbd>⇧ ⇧</kbd><span data-i18n="kbd.findFile">Find file</span>' +
|
|
313
|
+
'<kbd>⌘⇧F</kbd><span data-i18n="kbd.findInFiles">Find in files</span>' +
|
|
314
|
+
'<kbd>⌘E</kbd><span data-i18n="kbd.recentFiles">Recent files</span>' +
|
|
315
|
+
'<kbd>⌘B</kbd><span data-i18n="kbd.defUsages">Definition / usages</span>' +
|
|
316
|
+
'<kbd>⌘↓</kbd><span data-i18n="kbd.goToDef">Go to definition</span>' +
|
|
317
|
+
'<kbd>⌘⇧[ / ]</kbd><span data-i18n="kbd.prevNextTab">Prev / next tab</span>' +
|
|
318
|
+
'<kbd>⌘[ / ]</kbd><span data-i18n="kbd.cursorBackForward">Cursor back / forward</span>' +
|
|
319
|
+
'<kbd>⌥←/→</kbd><span data-i18n="kbd.wordJump">Word jump (vim w)</span>' +
|
|
320
|
+
'<kbd>⌘←/→</kbd><span data-i18n="kbd.lineStartEnd">Line start / end</span>' +
|
|
321
|
+
'<kbd>⇧←↑↓→</kbd><span data-i18n="kbd.extendSelection">Extend selection</span>' +
|
|
322
|
+
'<kbd>PageUp / PageDown</kbd><span data-i18n="kbd.pageUpDown">Page up / down</span>' +
|
|
323
|
+
'<kbd>⌘Enter / ⌥Enter</kbd><span data-i18n="kbd.runHttp">Run HTTP request (.http)</span>' +
|
|
324
|
+
'<kbd>⌘W</kbd><span data-i18n="kbd.closeTab">Close tab</span>' +
|
|
205
325
|
'</div>' +
|
|
206
326
|
'<div class="keys-cat" data-i18n="settings.kbd.cat.review">Review</div>' +
|
|
207
327
|
'<div class="keys-grid">' +
|
|
208
328
|
'<kbd><</kbd><span data-i18n="kbd.toggleViewed">Toggle viewed</span>' +
|
|
209
329
|
'<kbd>? ></kbd><span data-i18n="kbd.addQuestionChange">Add question / change</span>' +
|
|
210
|
-
'<kbd
|
|
211
|
-
'<kbd
|
|
212
|
-
'<kbd
|
|
213
|
-
'<kbd>
|
|
330
|
+
'<kbd>⌘⇧/ .</kbd><span data-i18n="kbd.allQuestionsChanges">All questions / changes</span>' +
|
|
331
|
+
'<kbd>⌘⇧W</kbd><span data-i18n="kbd.ignoreWhitespace">Ignore whitespace</span>' +
|
|
332
|
+
'<kbd>⌘Enter</kbd><span data-i18n="kbd.saveComment">Save comment</span>' +
|
|
333
|
+
'<kbd>e</kbd><span data-i18n="kbd.editComment">Edit comment (when selected)</span>' +
|
|
334
|
+
'<kbd>Backspace / Delete</kbd><span data-i18n="kbd.deleteComment">Delete comment (when selected)</span>' +
|
|
335
|
+
'<kbd>⌥↑/↓</kbd><span data-i18n="kbd.stepComments">Step between comments (merged)</span>' +
|
|
336
|
+
'<kbd>⌥Enter</kbd><span data-i18n="kbd.mergedSend">Comment menu / send to pane (merged)</span>' +
|
|
337
|
+
'<kbd>⌘⇧N</kbd><span data-i18n="kbd.promptMemo">Prompt memo</span>' +
|
|
338
|
+
'<kbd>⌘⇧'</kbd><span data-i18n="kbd.maximizePanel">Maximize panel</span>' +
|
|
214
339
|
'</div>' +
|
|
215
340
|
'<div class="keys-cat" data-i18n="settings.kbd.cat.terminal">Terminal</div>' +
|
|
216
341
|
'<div class="keys-grid">' +
|
|
217
|
-
'<kbd
|
|
218
|
-
'<kbd
|
|
219
|
-
'<kbd
|
|
220
|
-
'<kbd
|
|
221
|
-
'<kbd
|
|
342
|
+
'<kbd>⌃` / ⌥F12</kbd><span data-i18n="kbd.toggleTerminal">Toggle terminal</span>' +
|
|
343
|
+
'<kbd>⌘D</kbd><span data-i18n="kbd.splitPane">Split pane</span>' +
|
|
344
|
+
'<kbd>⌘⌥[ / ]</kbd><span data-i18n="kbd.focusPane">Focus prev / next pane</span>' +
|
|
345
|
+
'<kbd>⌘⌥R</kbd><span data-i18n="kbd.renamePane">Rename pane</span>' +
|
|
346
|
+
'<kbd>⌘W</kbd><span data-i18n="kbd.closeTerminal">Close terminal (when focused)</span>' +
|
|
222
347
|
'</div>' +
|
|
223
348
|
'</div>',
|
|
224
349
|
"</section>",
|
|
225
350
|
'<section class="settings-section hidden" data-cat="prompts">',
|
|
226
351
|
'<div class="settings-h" data-i18n="mergePrompts.title">Merge prompts</div>',
|
|
227
|
-
'<div class="settings-desc" data-i18n="mergePrompts.desc">Heading prepended to the merged prompt opened with
|
|
352
|
+
'<div class="settings-desc" data-i18n="mergePrompts.desc">Heading prepended to the merged prompt opened with ⌘⇧/ (questions) and ⌘⇧. (change requests). Leave blank to use the default.</div>',
|
|
228
353
|
'<label class="settings-label" for="settings-prompt-q" data-i18n="mergePrompts.qHeading">Questions heading</label>',
|
|
229
354
|
'<textarea id="settings-prompt-q" class="settings-textarea" rows="4" spellcheck="false"></textarea>',
|
|
230
355
|
'<label class="settings-label" for="settings-prompt-c" data-i18n="mergePrompts.cHeading">Change-requests heading</label>',
|
package/dist/util.d.ts
CHANGED
|
@@ -19,3 +19,8 @@ export declare function listRecentFiles(dir: string, limit: number): string[];
|
|
|
19
19
|
export declare function sanitizeTerminalEnv(env: NodeJS.ProcessEnv): {
|
|
20
20
|
[key: string]: string;
|
|
21
21
|
};
|
|
22
|
+
export declare function ensureUtf8Locale(env: {
|
|
23
|
+
[key: string]: string;
|
|
24
|
+
}): {
|
|
25
|
+
[key: string]: string;
|
|
26
|
+
};
|
package/dist/util.js
CHANGED
|
@@ -170,3 +170,24 @@ export function sanitizeTerminalEnv(env) {
|
|
|
170
170
|
}
|
|
171
171
|
return out;
|
|
172
172
|
}
|
|
173
|
+
// GUI launches (Finder double-click, Spotlight, the `mo` relauncher) often start with no LANG/LC_* at
|
|
174
|
+
// all, so the pty's shell — and tools it runs, notably git's `less` pager — fall back to the C locale and
|
|
175
|
+
// render UTF-8 text (e.g. Korean commit messages) as escaped bytes like "<EA><B5><AD>". Force a UTF-8
|
|
176
|
+
// codeset unless the inherited locale already is one. Mutates and returns the given object.
|
|
177
|
+
export function ensureUtf8Locale(env) {
|
|
178
|
+
const isUtf8 = (value) => !!value && /utf-?8/i.test(value);
|
|
179
|
+
// LC_ALL overrides LANG and every LC_* category; a non-UTF-8 LC_ALL (e.g. "C") would defeat the LANG we
|
|
180
|
+
// set below, so drop it and let LANG win.
|
|
181
|
+
if (env.LC_ALL && !isUtf8(env.LC_ALL))
|
|
182
|
+
delete env.LC_ALL;
|
|
183
|
+
if (isUtf8(env.LC_ALL) || isUtf8(env.LC_CTYPE) || isUtf8(env.LANG))
|
|
184
|
+
return env;
|
|
185
|
+
// Same reasoning for a stray non-UTF-8 LC_CTYPE — it overrides LANG for character handling.
|
|
186
|
+
if (env.LC_CTYPE && !isUtf8(env.LC_CTYPE))
|
|
187
|
+
delete env.LC_CTYPE;
|
|
188
|
+
// Preserve the user's region when LANG names a real locale (ko_KR -> ko_KR.UTF-8); otherwise (C/POSIX/
|
|
189
|
+
// empty) fall back to en_US.UTF-8, which always exists on macOS.
|
|
190
|
+
const base = env.LANG && /^[A-Za-z]{2}_[A-Za-z]{2}/.test(env.LANG) ? env.LANG.split(".")[0] : "en_US";
|
|
191
|
+
env.LANG = base + ".UTF-8";
|
|
192
|
+
return env;
|
|
193
|
+
}
|