|
157 | 157 | .command-row { |
158 | 158 | display: flex; |
159 | 159 | gap: 8px; |
160 | | - align-items: center; |
| 160 | + align-items: flex-start; |
161 | 161 | } |
162 | 162 |
|
163 | 163 | .command-input { |
|
171 | 171 | font-size: 13px; |
172 | 172 | outline: none; |
173 | 173 | transition: border-color 0.15s; |
| 174 | + resize: none; |
| 175 | + overflow: hidden; |
| 176 | + min-height: 36px; |
| 177 | + max-height: 200px; |
| 178 | + line-height: 1.5; |
174 | 179 | } |
175 | 180 | .command-input:focus { border-color: #58a6ff; } |
176 | 181 | .command-input::placeholder { color: #6e7681; } |
177 | 182 | .command-input:disabled { opacity: 0.5; } |
178 | 183 |
|
| 184 | + .command-actions { |
| 185 | + display: flex; |
| 186 | + flex-direction: column; |
| 187 | + gap: 4px; |
| 188 | + align-items: flex-end; |
| 189 | + flex-shrink: 0; |
| 190 | + } |
| 191 | +
|
179 | 192 | .hint { |
180 | 193 | font-size: 11px; |
181 | 194 | color: #484f58; |
|
409 | 422 | </div> |
410 | 423 | <!-- Command input --> |
411 | 424 | <div class="command-row"> |
412 | | - <input |
413 | | - type="text" |
| 425 | + <textarea |
414 | 426 | class="command-input" |
415 | 427 | id="cmdInput" |
416 | 428 | placeholder="<%- i18nKeys.cmdPlaceholder %>" |
| 429 | + rows="1" |
417 | 430 | autocomplete="off" |
418 | 431 | autocorrect="off" |
419 | 432 | autocapitalize="off" |
420 | 433 | spellcheck="false" |
421 | | - > |
422 | | - <button class="btn btn-primary" id="btnExecute" onclick="doExecute()" disabled> |
423 | | - <i class="ti ti-player-play"></i> <%- i18nKeys.execute %> |
424 | | - </button> |
425 | | - <button class="btn btn-danger" id="btnCancel" onclick="doCancel()" style="display:none"> |
426 | | - <i class="ti ti-player-stop"></i> <%- i18nKeys.cancel %> |
427 | | - </button> |
428 | | - <span class="hint"><%- typeof t !== 'undefined' ? t('broadcast.hintEnter') : 'Enter to execute' %></span> |
| 434 | + ></textarea> |
| 435 | + <div class="command-actions"> |
| 436 | + <button class="btn btn-primary" id="btnExecute" onclick="doExecute()" disabled> |
| 437 | + <i class="ti ti-player-play"></i> <%- i18nKeys.execute %> |
| 438 | + </button> |
| 439 | + <button class="btn btn-danger" id="btnCancel" onclick="doCancel()" style="display:none"> |
| 440 | + <i class="ti ti-player-stop"></i> <%- i18nKeys.cancel %> |
| 441 | + </button> |
| 442 | + <span class="hint">Ctrl+Enter</span> |
| 443 | + </div> |
429 | 444 | </div> |
430 | 445 | </div> |
431 | 446 |
|
|
765 | 780 | return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); |
766 | 781 | } |
767 | 782 |
|
| 783 | + // ── Textarea auto-resize ────────────────────────────────────────── |
| 784 | + function autoResize(el) { |
| 785 | + el.style.height = 'auto'; |
| 786 | + el.style.overflow = 'hidden'; |
| 787 | + const next = Math.min(el.scrollHeight, 200); |
| 788 | + el.style.height = next + 'px'; |
| 789 | + if (el.scrollHeight > 200) el.style.overflow = 'auto'; |
| 790 | + } |
| 791 | +
|
768 | 792 | // ── Keyboard shortcuts ──────────────────────────────────────────── |
769 | 793 | document.addEventListener('DOMContentLoaded', () => { |
770 | 794 | const input = document.getElementById('cmdInput'); |
771 | 795 |
|
| 796 | + input.addEventListener('input', () => autoResize(input)); |
| 797 | +
|
772 | 798 | input.addEventListener('keydown', (e) => { |
773 | | - if (e.key === 'Enter') { |
| 799 | + const multiline = input.value.includes('\n'); |
| 800 | +
|
| 801 | + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { |
774 | 802 | e.preventDefault(); |
775 | 803 | doExecute(); |
776 | | - } else if (e.key === 'ArrowUp') { |
777 | | - e.preventDefault(); |
778 | | - if (cmdHistoryIdx < cmdHistory.length - 1) { |
| 804 | + } else if (e.key === 'ArrowUp' && !multiline) { |
| 805 | + const cursorAtStart = input.selectionStart === 0; |
| 806 | + if (cursorAtStart && cmdHistoryIdx < cmdHistory.length - 1) { |
| 807 | + e.preventDefault(); |
779 | 808 | cmdHistoryIdx++; |
780 | 809 | input.value = cmdHistory[cmdHistoryIdx]; |
| 810 | + autoResize(input); |
781 | 811 | } |
782 | | - } else if (e.key === 'ArrowDown') { |
783 | | - e.preventDefault(); |
784 | | - if (cmdHistoryIdx > 0) { |
785 | | - cmdHistoryIdx--; |
786 | | - input.value = cmdHistory[cmdHistoryIdx]; |
787 | | - } else { |
788 | | - cmdHistoryIdx = -1; |
789 | | - input.value = ''; |
| 812 | + } else if (e.key === 'ArrowDown' && !multiline) { |
| 813 | + const cursorAtEnd = input.selectionStart === input.value.length; |
| 814 | + if (cursorAtEnd) { |
| 815 | + e.preventDefault(); |
| 816 | + if (cmdHistoryIdx > 0) { |
| 817 | + cmdHistoryIdx--; |
| 818 | + input.value = cmdHistory[cmdHistoryIdx]; |
| 819 | + } else { |
| 820 | + cmdHistoryIdx = -1; |
| 821 | + input.value = ''; |
| 822 | + } |
| 823 | + autoResize(input); |
790 | 824 | } |
791 | 825 | } |
792 | 826 | }); |
|
0 commit comments