@happy-nut/monacori 0.1.12 → 0.1.13
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/dist/i18n.js +2 -0
- package/dist/render.js +2 -2
- package/dist/viewer.client.js +86 -22
- package/dist/viewer.css +8 -0
- package/package.json +1 -1
package/dist/i18n.js
CHANGED
|
@@ -131,6 +131,7 @@ export const MESSAGES = {
|
|
|
131
131
|
"merged.close": "Close",
|
|
132
132
|
"dropdown.navigate": "Go to comment",
|
|
133
133
|
"dropdown.remove": "Remove",
|
|
134
|
+
"toast.commentsDropped": "Removed {n} comment(s) on {file} — the file changed too much to track them",
|
|
134
135
|
"merged.qHeading": "# Questions",
|
|
135
136
|
"merged.cHeading": "# Change requests",
|
|
136
137
|
// Prompt memo (Cmd/Ctrl+Shift+N) — a single freeform Markdown scratchpad with a live split preview.
|
|
@@ -262,6 +263,7 @@ export const MESSAGES = {
|
|
|
262
263
|
"merged.close": "닫기",
|
|
263
264
|
"dropdown.navigate": "코멘트로 이동",
|
|
264
265
|
"dropdown.remove": "지우기",
|
|
266
|
+
"toast.commentsDropped": "{file}이(가) 변경되어 추적할 수 없는 코멘트 {n}개를 제거했습니다",
|
|
265
267
|
// Structural markers stay English in both locales (the preamble prose below follows the locale).
|
|
266
268
|
"merged.qHeading": "# Questions",
|
|
267
269
|
"merged.cHeading": "# Change requests",
|
package/dist/render.js
CHANGED
|
@@ -180,9 +180,9 @@ export function renderDiffHtml(input) {
|
|
|
180
180
|
'<div id="app-info-status" class="app-info-status" data-i18n="settings.checkingUpdates">Checking for updates…</div>',
|
|
181
181
|
'<button type="button" id="app-info-update" class="plain-button app-info-update hidden" data-i18n="settings.updateRestart">Update & Restart</button>',
|
|
182
182
|
'<label class="settings-label" for="settings-language" data-i18n="settings.language">Language</label>',
|
|
183
|
-
'<
|
|
183
|
+
'<button type="button" id="settings-language" class="settings-select mc-select" data-i18n-aria="settings.language"></button>',
|
|
184
184
|
'<label class="settings-label" for="settings-theme" data-i18n="settings.theme">Theme</label>',
|
|
185
|
-
'<
|
|
185
|
+
'<button type="button" id="settings-theme" class="settings-select mc-select" data-i18n-aria="settings.theme"></button>',
|
|
186
186
|
'<div class="app-info-keys">' +
|
|
187
187
|
'<div class="app-info-keys-h" data-i18n="settings.kbd.title">Keyboard shortcuts</div>' +
|
|
188
188
|
'<div class="keys-cat" data-i18n="settings.kbd.cat.nav">Navigation</div>' +
|
package/dist/viewer.client.js
CHANGED
|
@@ -147,14 +147,36 @@ var locale = (function () {
|
|
|
147
147
|
return (v === 'ko' || v === 'en') ? v : 'en';
|
|
148
148
|
})();
|
|
149
149
|
function t(key) { var m = (I18N[locale] || I18N.en || {}); return (m && key in m) ? m[key] : ((I18N.en && I18N.en[key]) || key); }
|
|
150
|
+
var langSelectRef = null, themeSelectRef = null;
|
|
151
|
+
// Replace a native <select> with a button that opens our custom dropdown (consistent with the comment
|
|
152
|
+
// dropdown + themable; native <select> popups ignore the app theme). getOptions() -> [{value,label}];
|
|
153
|
+
// returns { render } so localized labels can be refreshed on a language switch.
|
|
154
|
+
function setupCustomSelect(id, getOptions, getCurrent, onPick) {
|
|
155
|
+
var el = document.getElementById(id);
|
|
156
|
+
if (!el) return null;
|
|
157
|
+
function render() {
|
|
158
|
+
var cur = getCurrent();
|
|
159
|
+
var match = getOptions().filter(function (o) { return o.value === cur; })[0];
|
|
160
|
+
el.textContent = match ? match.label : cur;
|
|
161
|
+
}
|
|
162
|
+
el.addEventListener('click', function (e) {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
var r = el.getBoundingClientRect(), cur = getCurrent();
|
|
165
|
+
showCustomDropdown(r.left, r.bottom + 4, getOptions().map(function (o) {
|
|
166
|
+
return { label: (o.value === cur ? '✓ ' : ' ') + o.label, onSelect: function () { onPick(o.value); render(); } };
|
|
167
|
+
}));
|
|
168
|
+
});
|
|
169
|
+
render();
|
|
170
|
+
return { render: render };
|
|
171
|
+
}
|
|
150
172
|
function applyI18n() {
|
|
151
173
|
document.querySelectorAll('[data-i18n]').forEach(function (el) { el.textContent = t(el.getAttribute('data-i18n')); });
|
|
152
174
|
document.querySelectorAll('[data-i18n-ph]').forEach(function (el) { el.setAttribute('placeholder', t(el.getAttribute('data-i18n-ph'))); });
|
|
153
175
|
document.querySelectorAll('[data-i18n-title]').forEach(function (el) { el.setAttribute('title', t(el.getAttribute('data-i18n-title'))); });
|
|
154
176
|
document.querySelectorAll('[data-i18n-aria]').forEach(function (el) { el.setAttribute('aria-label', t(el.getAttribute('data-i18n-aria'))); });
|
|
155
177
|
document.documentElement.lang = locale;
|
|
156
|
-
|
|
157
|
-
if (
|
|
178
|
+
if (langSelectRef) langSelectRef.render();
|
|
179
|
+
if (themeSelectRef) themeSelectRef.render(); // theme labels are localized — refresh on a language switch too
|
|
158
180
|
}
|
|
159
181
|
// Theme mirrors the locale pattern: persisted choice, applied by toggling data-theme on <html> so the
|
|
160
182
|
// :root[data-theme="light"] palette takes over. Dark is the default (matches the inline :root). Applied
|
|
@@ -167,8 +189,7 @@ var theme = (function () {
|
|
|
167
189
|
})();
|
|
168
190
|
function applyTheme() {
|
|
169
191
|
document.documentElement.setAttribute('data-theme', theme);
|
|
170
|
-
|
|
171
|
-
if (sel) sel.value = theme;
|
|
192
|
+
if (themeSelectRef) themeSelectRef.render();
|
|
172
193
|
}
|
|
173
194
|
applyTheme();
|
|
174
195
|
let fileStates = JSON.parse(document.getElementById('file-state-data')?.textContent || '[]');
|
|
@@ -213,6 +234,7 @@ function loadSourceData() {
|
|
|
213
234
|
sourceLoaded = true;
|
|
214
235
|
sourceLoading = false;
|
|
215
236
|
scheduleSymbolIndex();
|
|
237
|
+
remapComments(); // content just arrived — reconcile comment anchors against it
|
|
216
238
|
if (pendingSourceOpen) { var po = pendingSourceOpen; pendingSourceOpen = null; openSourceFile(po.path, po.shouldSwitch); }
|
|
217
239
|
else if (isSourceViewerVisible() && document.getElementById('source-viewer').dataset.openPath) { openSourceFile(document.getElementById('source-viewer').dataset.openPath, false); }
|
|
218
240
|
if (pendingSymbol) { var s = pendingSymbol; pendingSymbol = null; goToDefOrUsages(s); }
|
|
@@ -1706,6 +1728,52 @@ function handleDiffCaretKey(event) {
|
|
|
1706
1728
|
|
|
1707
1729
|
// ===== Review comments: questions ("?") and change-requests (">") =====
|
|
1708
1730
|
// (COMMENTS_KEY / reviewComments / commentSeq / composerState are declared near the top of the script)
|
|
1731
|
+
// Bottom-left, non-blocking toast stack; each toast auto-dismisses. Used to tell the user when a file
|
|
1732
|
+
// change made some comments untrackable (they were removed).
|
|
1733
|
+
function showToast(message) {
|
|
1734
|
+
var stack = document.getElementById('mc-toasts');
|
|
1735
|
+
if (!stack) { stack = document.createElement('div'); stack.id = 'mc-toasts'; document.body.appendChild(stack); }
|
|
1736
|
+
var el = document.createElement('div');
|
|
1737
|
+
el.className = 'mc-toast';
|
|
1738
|
+
el.textContent = message;
|
|
1739
|
+
stack.appendChild(el);
|
|
1740
|
+
requestAnimationFrame(function () { el.classList.add('show'); });
|
|
1741
|
+
setTimeout(function () {
|
|
1742
|
+
el.classList.add('hide');
|
|
1743
|
+
setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
|
|
1744
|
+
}, 4500);
|
|
1745
|
+
}
|
|
1746
|
+
// When a file changes, follow each comment to its snapshot line (c.code) in the new content: same line if
|
|
1747
|
+
// unchanged, else the nearest exact match of that line. If the line can't be found the change is too large
|
|
1748
|
+
// to trust — drop the comment and toast. Files whose content isn't loaded yet (lazy) are skipped here and
|
|
1749
|
+
// reconciled once loadSourceData brings the content in.
|
|
1750
|
+
function remapComments() {
|
|
1751
|
+
if (!reviewComments.length) return;
|
|
1752
|
+
var dropped = [], moved = 0;
|
|
1753
|
+
reviewComments = reviewComments.filter(function (c) {
|
|
1754
|
+
var file = sourceByPath.get(c.path);
|
|
1755
|
+
if (!file || !file.embedded || typeof file.content !== 'string' || !file.content) return true;
|
|
1756
|
+
var code = c.code == null ? '' : String(c.code);
|
|
1757
|
+
if (!code.trim()) return true;
|
|
1758
|
+
var lines = file.content.split(/\r?\n/);
|
|
1759
|
+
if (lines[c.line - 1] === code) return true;
|
|
1760
|
+
var best = -1, bestDist = Infinity;
|
|
1761
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1762
|
+
if (lines[i] === code) { var d = Math.abs(i - (c.line - 1)); if (d < bestDist) { bestDist = d; best = i; } }
|
|
1763
|
+
}
|
|
1764
|
+
if (best >= 0) { if (c.line !== best + 1) moved++; c.line = best + 1; return true; }
|
|
1765
|
+
dropped.push(c);
|
|
1766
|
+
return false;
|
|
1767
|
+
});
|
|
1768
|
+
if (!dropped.length && !moved) return; // nothing changed — skip the save/re-render
|
|
1769
|
+
saveComments();
|
|
1770
|
+
var byPath = {};
|
|
1771
|
+
dropped.forEach(function (c) { byPath[c.path] = (byPath[c.path] || 0) + 1; });
|
|
1772
|
+
Object.keys(byPath).forEach(function (p) {
|
|
1773
|
+
showToast(t('toast.commentsDropped').replace('{n}', byPath[p]).replace('{file}', String(p).split('/').pop()));
|
|
1774
|
+
});
|
|
1775
|
+
refreshComments();
|
|
1776
|
+
}
|
|
1709
1777
|
function saveComments() {
|
|
1710
1778
|
persistSave(COMMENTS_KEY, reviewComments);
|
|
1711
1779
|
}
|
|
@@ -1756,14 +1824,16 @@ function currentCommentTarget() {
|
|
|
1756
1824
|
var f = Math.min(sa, sb), t = Math.max(sa, sb);
|
|
1757
1825
|
return { path: viewerCursor.path, line: t + 1, code: selText, from: f + 1, to: t + 1, side: null };
|
|
1758
1826
|
}
|
|
1759
|
-
|
|
1827
|
+
var scaretFile = sourceByPath.get(viewerCursor.path);
|
|
1828
|
+
var scaretCode = (scaretFile && typeof scaretFile.content === 'string') ? (scaretFile.content.split(/\r?\n/)[viewerCursor.lineIndex] || '') : '';
|
|
1829
|
+
return { path: viewerCursor.path, line: viewerCursor.lineIndex + 1, code: scaretCode, from: null, to: null, side: null };
|
|
1760
1830
|
}
|
|
1761
1831
|
// Diff view: prefer the explicit diff caret when there is no text selection.
|
|
1762
1832
|
if (!hasSel && diffCursor && isDiffViewVisible()) {
|
|
1763
1833
|
var dwrap = diffWrapperByPath(diffCursor.path);
|
|
1764
1834
|
var drow = dwrap ? diffRowAt(dwrap, diffCursor.side, diffCursor.rowIndex) : null;
|
|
1765
1835
|
var dline = drow ? diffLineNumber(drow) : null;
|
|
1766
|
-
if (dline != null) return { path: diffCursor.path, line: dline, code: '', from: null, to: null, side: null };
|
|
1836
|
+
if (dline != null) return { path: diffCursor.path, line: dline, code: diffLineText(drow) || '', from: null, to: null, side: null };
|
|
1767
1837
|
}
|
|
1768
1838
|
// Diff view with a selection (or click): anchor at the LAST line so the composer drops BELOW the
|
|
1769
1839
|
// drag; capture the selected code + line span (used to keep the drag highlighted via .mc-sel-line).
|
|
@@ -2751,35 +2821,28 @@ if (window.monacoriMenu && typeof window.monacoriMenu.onCloseTab === 'function')
|
|
|
2751
2821
|
if (resetBtn) resetBtn.addEventListener('click', function () { saveMergePrompt('q', ''); saveMergePrompt('c', ''); fill(); flash(); });
|
|
2752
2822
|
// Language: live-switch the whole UI (no reload). Persist, re-apply the static chrome, then re-render
|
|
2753
2823
|
// any currently-shown dynamic text (open composer / merged modal / index status) so it follows too.
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
var next = langSel.value === 'ko' ? 'ko' : 'en';
|
|
2824
|
+
langSelectRef = setupCustomSelect('settings-language',
|
|
2825
|
+
function () { return [{ value: 'en', label: 'English' }, { value: 'ko', label: '한국어' }]; },
|
|
2826
|
+
function () { return locale; },
|
|
2827
|
+
function (next) {
|
|
2759
2828
|
if (next === locale) return;
|
|
2760
2829
|
locale = next;
|
|
2761
2830
|
persistSave(LOCALE_KEY, locale);
|
|
2762
2831
|
applyI18n();
|
|
2763
|
-
//
|
|
2764
|
-
fill();
|
|
2765
|
-
// Re-render dynamic, currently-visible text in the new locale.
|
|
2832
|
+
fill(); // merge-prompt placeholders are locale-dependent defaults
|
|
2766
2833
|
try { if (typeof refreshComments === 'function') refreshComments(); } catch (e) {}
|
|
2767
2834
|
var mergedModal = document.getElementById('mc-modal');
|
|
2768
2835
|
if (mergedModal) { var mk = mergedModal.dataset.kind || 'q'; mergedModal.remove(); openMergedView(mk); }
|
|
2769
2836
|
});
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
themeSel.value = theme;
|
|
2775
|
-
themeSel.addEventListener('change', function () {
|
|
2776
|
-
var next = themeSel.value === 'light' ? 'light' : 'dark';
|
|
2837
|
+
themeSelectRef = setupCustomSelect('settings-theme',
|
|
2838
|
+
function () { return [{ value: 'dark', label: t('theme.dark') }, { value: 'light', label: t('theme.light') }]; },
|
|
2839
|
+
function () { return theme; },
|
|
2840
|
+
function (next) {
|
|
2777
2841
|
if (next === theme) return;
|
|
2778
2842
|
theme = next;
|
|
2779
2843
|
persistSave(THEME_KEY, theme);
|
|
2780
2844
|
applyTheme();
|
|
2781
2845
|
});
|
|
2782
|
-
}
|
|
2783
2846
|
})();
|
|
2784
2847
|
|
|
2785
2848
|
function setTab(name) {
|
|
@@ -2934,6 +2997,7 @@ function applyDiffUpdate(u) {
|
|
|
2934
2997
|
applyI18n();
|
|
2935
2998
|
populateHttpEnvSelect();
|
|
2936
2999
|
initSourceTreeFolds();
|
|
3000
|
+
remapComments(); // follow/drop comments whose anchor line moved or vanished in the new build
|
|
2937
3001
|
refreshComments();
|
|
2938
3002
|
|
|
2939
3003
|
// 5) Best-effort restore of what the user was looking at.
|
package/dist/viewer.css
CHANGED
|
@@ -262,6 +262,10 @@ body {
|
|
|
262
262
|
font: 12px/1.55 ui-sans-serif, system-ui, sans-serif;
|
|
263
263
|
}
|
|
264
264
|
.settings-select:focus { outline: none; border-color: var(--active); }
|
|
265
|
+
/* The settings dropdowns are buttons (custom dropdown), not native <select> — style them to read like one. */
|
|
266
|
+
.settings-select.mc-select { text-align: left; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; min-width: 130px; }
|
|
267
|
+
.settings-select.mc-select::after { content: '▾'; margin-left: auto; opacity: 0.55; }
|
|
268
|
+
.settings-select.mc-select:hover { border-color: var(--active); }
|
|
265
269
|
.settings-actions { display: flex; align-items: center; gap: 12px; margin-top: 18px; }
|
|
266
270
|
.settings-saved { font-size: 12px; color: var(--active); }
|
|
267
271
|
.app-info-keys-h { font-weight: 600; color: var(--text); margin-bottom: 8px; }
|
|
@@ -859,6 +863,10 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
|
859
863
|
.mc-dropdown { position: fixed; z-index: 70; min-width: 150px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); padding: 4px; }
|
|
860
864
|
.mc-dropdown-item { display: block; width: 100%; text-align: left; padding: 7px 12px; border: 0; background: transparent; color: var(--text); border-radius: 6px; font-size: 13px; cursor: pointer; white-space: nowrap; }
|
|
861
865
|
.mc-dropdown-item.active, .mc-dropdown-item:hover { background: var(--active); color: #fff; }
|
|
866
|
+
#mc-toasts { position: fixed; left: 16px; bottom: 16px; z-index: 80; display: flex; flex-direction: column; gap: 8px; max-width: 360px; pointer-events: none; }
|
|
867
|
+
.mc-toast { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-left: 3px solid var(--active); border-radius: 8px; padding: 10px 14px; font-size: 13px; line-height: 1.45; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); opacity: 0; transform: translateY(8px); transition: opacity .25s ease, transform .25s ease; }
|
|
868
|
+
.mc-toast.show { opacity: 1; transform: translateY(0); }
|
|
869
|
+
.mc-toast.hide { opacity: 0; transform: translateY(8px); }
|
|
862
870
|
/* Prompt memo: split editor | live Markdown preview inside the standard modal shell. */
|
|
863
871
|
.mc-memo-body { display: grid; grid-template-columns: 1fr 1fr; min-height: 0; height: 100%; }
|
|
864
872
|
.mc-memo-edit { height: 100%; border-right: 1px solid var(--border); }
|