@happy-nut/monacori 0.1.12 → 0.1.14
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 +123 -31
- package/dist/viewer.css +10 -2
- 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
|
+
}), r.top - 4);
|
|
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).
|
|
@@ -2021,7 +2091,28 @@ function saveMergePrompt(kind, text) {
|
|
|
2021
2091
|
|
|
2022
2092
|
// Reusable custom dropdown (keyboard + mouse). options: [{ label, onSelect }]. First item is pre-selected;
|
|
2023
2093
|
// Arrow keys move, Enter chooses, Esc / click-outside dismiss. Replaces native <select>/menus everywhere.
|
|
2024
|
-
|
|
2094
|
+
// Approximate the caret's pixel position in the (monospace) merged textarea so the dropdown can open right
|
|
2095
|
+
// under it — and flip above when there isn't room below. Returns { x, top (caret line top), below }.
|
|
2096
|
+
function mergedCaretXY(area) {
|
|
2097
|
+
var pos = area.selectionStart || 0;
|
|
2098
|
+
var nl = area.value.slice(0, pos).split('\n');
|
|
2099
|
+
var lineNum = nl.length - 1;
|
|
2100
|
+
var col = nl[lineNum].length;
|
|
2101
|
+
var cs = getComputedStyle(area);
|
|
2102
|
+
var lineH = parseFloat(cs.lineHeight) || 18;
|
|
2103
|
+
var rect = area.getBoundingClientRect();
|
|
2104
|
+
var span = document.createElement('span');
|
|
2105
|
+
span.style.cssText = 'position:absolute;visibility:hidden;white-space:pre';
|
|
2106
|
+
span.style.font = cs.font;
|
|
2107
|
+
span.textContent = 'MMMMMMMMMMMMMMMMMMMM';
|
|
2108
|
+
document.body.appendChild(span);
|
|
2109
|
+
var charW = span.getBoundingClientRect().width / 20;
|
|
2110
|
+
span.remove();
|
|
2111
|
+
var caretTop = rect.top + (parseFloat(cs.paddingTop) || 0) + lineNum * lineH - area.scrollTop;
|
|
2112
|
+
var x = Math.min(rect.left + (parseFloat(cs.paddingLeft) || 0) + col * charW, rect.right - 24);
|
|
2113
|
+
return { x: x, top: caretTop, below: caretTop + lineH + 2 };
|
|
2114
|
+
}
|
|
2115
|
+
function showCustomDropdown(x, y, options, flipTop) {
|
|
2025
2116
|
var existing = document.getElementById('mc-dropdown');
|
|
2026
2117
|
if (existing) existing.remove();
|
|
2027
2118
|
var dd = document.createElement('div');
|
|
@@ -2046,9 +2137,16 @@ function showCustomDropdown(x, y, options) {
|
|
|
2046
2137
|
item.addEventListener('mousemove', function () { setActive(i); });
|
|
2047
2138
|
dd.appendChild(item);
|
|
2048
2139
|
});
|
|
2049
|
-
dd.style.left = Math.round(x) + 'px';
|
|
2050
|
-
dd.style.top = Math.round(y) + 'px';
|
|
2051
2140
|
document.body.appendChild(dd);
|
|
2141
|
+
// Position after measuring: open at (x, y); flip above (flipTop) when it would overflow the bottom,
|
|
2142
|
+
// and nudge in from the right/bottom edges so it never clips offscreen.
|
|
2143
|
+
var ddr = dd.getBoundingClientRect();
|
|
2144
|
+
var top = y, left = x;
|
|
2145
|
+
if (typeof flipTop === 'number' && top + ddr.height > window.innerHeight - 8) top = Math.max(8, flipTop - ddr.height);
|
|
2146
|
+
else if (top + ddr.height > window.innerHeight - 8) top = Math.max(8, window.innerHeight - ddr.height - 8);
|
|
2147
|
+
if (left + ddr.width > window.innerWidth - 8) left = Math.max(8, window.innerWidth - ddr.width - 8);
|
|
2148
|
+
dd.style.left = Math.round(left) + 'px';
|
|
2149
|
+
dd.style.top = Math.round(top) + 'px';
|
|
2052
2150
|
document.addEventListener('keydown', onKey, true);
|
|
2053
2151
|
document.addEventListener('mousedown', onOutside, true);
|
|
2054
2152
|
}
|
|
@@ -2151,20 +2249,20 @@ function openMergedView(kind) {
|
|
|
2151
2249
|
e.stopPropagation();
|
|
2152
2250
|
var seqs = mergedCommentSeqs(kind, area.selectionStart, area.selectionEnd);
|
|
2153
2251
|
if (!seqs.length) return;
|
|
2154
|
-
var
|
|
2155
|
-
var x =
|
|
2252
|
+
var cxy = mergedCaretXY(area);
|
|
2253
|
+
var x = cxy.x, y = cxy.below, flipTop = cxy.top;
|
|
2156
2254
|
var rerender = function () {
|
|
2157
2255
|
if (!reviewComments.filter(function (c) { return c.kind === kind; }).length) { modal.remove(); return; }
|
|
2158
2256
|
area.value = buildMergedText(kind);
|
|
2159
2257
|
};
|
|
2160
2258
|
if (area.selectionStart !== area.selectionEnd || seqs.length > 1) {
|
|
2161
|
-
showCustomDropdown(x, y, [{ label: t('dropdown.remove'), onSelect: function () { seqs.forEach(deleteComment); rerender(); } }]);
|
|
2259
|
+
showCustomDropdown(x, y, [{ label: t('dropdown.remove'), onSelect: function () { seqs.forEach(deleteComment); rerender(); } }], flipTop);
|
|
2162
2260
|
} else {
|
|
2163
2261
|
var seq = seqs[0];
|
|
2164
2262
|
showCustomDropdown(x, y, [
|
|
2165
2263
|
{ label: t('dropdown.navigate'), onSelect: function () { modal.remove(); navigateToComment(seq); } },
|
|
2166
2264
|
{ label: t('dropdown.remove'), onSelect: function () { deleteComment(seq); rerender(); } },
|
|
2167
|
-
]);
|
|
2265
|
+
], flipTop);
|
|
2168
2266
|
}
|
|
2169
2267
|
});
|
|
2170
2268
|
closeBtn.addEventListener('click', function () { modal.remove(); });
|
|
@@ -2194,12 +2292,12 @@ function openMergedView(kind) {
|
|
|
2194
2292
|
document.body.appendChild(modal);
|
|
2195
2293
|
// Focus the send button (Enter starts pane-pick) when present, else the read-only text. Electron
|
|
2196
2294
|
// async-restores focus to <body>, so retry briefly (same as the composer).
|
|
2197
|
-
var modalFocusTarget =
|
|
2295
|
+
var modalFocusTarget = area; // focus the text (not the send button) so the caret is visible and Opt+Arrow/Enter work; Send-to-terminal is a click
|
|
2198
2296
|
var modalFocusTries = 0;
|
|
2199
2297
|
var tryFocusModal = function () {
|
|
2200
2298
|
if (!document.getElementById('mc-modal')) return true;
|
|
2201
2299
|
if (document.activeElement === modalFocusTarget) return true;
|
|
2202
|
-
try { modalFocusTarget.focus();
|
|
2300
|
+
try { modalFocusTarget.focus(); modalFocusTarget.selectionStart = modalFocusTarget.selectionEnd = 0; } catch (e) {}
|
|
2203
2301
|
return document.activeElement === modalFocusTarget;
|
|
2204
2302
|
};
|
|
2205
2303
|
if (!tryFocusModal()) {
|
|
@@ -2751,35 +2849,28 @@ if (window.monacoriMenu && typeof window.monacoriMenu.onCloseTab === 'function')
|
|
|
2751
2849
|
if (resetBtn) resetBtn.addEventListener('click', function () { saveMergePrompt('q', ''); saveMergePrompt('c', ''); fill(); flash(); });
|
|
2752
2850
|
// Language: live-switch the whole UI (no reload). Persist, re-apply the static chrome, then re-render
|
|
2753
2851
|
// 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';
|
|
2852
|
+
langSelectRef = setupCustomSelect('settings-language',
|
|
2853
|
+
function () { return [{ value: 'en', label: 'English' }, { value: 'ko', label: '한국어' }]; },
|
|
2854
|
+
function () { return locale; },
|
|
2855
|
+
function (next) {
|
|
2759
2856
|
if (next === locale) return;
|
|
2760
2857
|
locale = next;
|
|
2761
2858
|
persistSave(LOCALE_KEY, locale);
|
|
2762
2859
|
applyI18n();
|
|
2763
|
-
//
|
|
2764
|
-
fill();
|
|
2765
|
-
// Re-render dynamic, currently-visible text in the new locale.
|
|
2860
|
+
fill(); // merge-prompt placeholders are locale-dependent defaults
|
|
2766
2861
|
try { if (typeof refreshComments === 'function') refreshComments(); } catch (e) {}
|
|
2767
2862
|
var mergedModal = document.getElementById('mc-modal');
|
|
2768
2863
|
if (mergedModal) { var mk = mergedModal.dataset.kind || 'q'; mergedModal.remove(); openMergedView(mk); }
|
|
2769
2864
|
});
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
themeSel.value = theme;
|
|
2775
|
-
themeSel.addEventListener('change', function () {
|
|
2776
|
-
var next = themeSel.value === 'light' ? 'light' : 'dark';
|
|
2865
|
+
themeSelectRef = setupCustomSelect('settings-theme',
|
|
2866
|
+
function () { return [{ value: 'dark', label: t('theme.dark') }, { value: 'light', label: t('theme.light') }]; },
|
|
2867
|
+
function () { return theme; },
|
|
2868
|
+
function (next) {
|
|
2777
2869
|
if (next === theme) return;
|
|
2778
2870
|
theme = next;
|
|
2779
2871
|
persistSave(THEME_KEY, theme);
|
|
2780
2872
|
applyTheme();
|
|
2781
2873
|
});
|
|
2782
|
-
}
|
|
2783
2874
|
})();
|
|
2784
2875
|
|
|
2785
2876
|
function setTab(name) {
|
|
@@ -2934,6 +3025,7 @@ function applyDiffUpdate(u) {
|
|
|
2934
3025
|
applyI18n();
|
|
2935
3026
|
populateHttpEnvSelect();
|
|
2936
3027
|
initSourceTreeFolds();
|
|
3028
|
+
remapComments(); // follow/drop comments whose anchor line moved or vanished in the new build
|
|
2937
3029
|
refreshComments();
|
|
2938
3030
|
|
|
2939
3031
|
// 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; }
|
|
@@ -856,9 +860,13 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
|
856
860
|
.mc-modal-head span { margin-right: auto; }
|
|
857
861
|
.mc-modal-text { width: 100%; height: 100%; box-sizing: border-box; resize: none; border: 0; padding: 12px; background: var(--bg); color: var(--text); caret-color: var(--text); font: 12px/1.55 Monaco, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
858
862
|
.mc-modal-text:focus { outline: none; }
|
|
859
|
-
.mc-dropdown { position: fixed; z-index: 70; min-width:
|
|
860
|
-
.mc-dropdown-item { display: block; width: 100%; text-align: left; padding:
|
|
863
|
+
.mc-dropdown { position: fixed; z-index: 70; min-width: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4); padding: 3px; }
|
|
864
|
+
.mc-dropdown-item { display: block; width: 100%; text-align: left; padding: 4px 10px; border: 0; background: transparent; color: var(--text); border-radius: 4px; font-size: 12px; line-height: 1.5; 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); }
|