|
21 | 21 | let editingConfig = $state<any>(null); |
22 | 22 | let showScriptModal = $state(false); |
23 | 23 | let scriptDraft = $state(''); |
| 24 | + let scriptTextarea: HTMLTextAreaElement | undefined = $state(); |
| 25 | + let lineNumbers = $derived((() => { |
| 26 | + const count = (scriptDraft || '').split('\n').length; |
| 27 | + return Array.from({ length: Math.max(count, 1) }, (_, i) => i + 1); |
| 28 | + })()); |
| 29 | +
|
| 30 | + function syncLineScroll(e: Event) { |
| 31 | + const textarea = e.target as HTMLTextAreaElement; |
| 32 | + const gutter = textarea.parentElement?.querySelector('.line-gutter') as HTMLElement | null; |
| 33 | + if (gutter) { |
| 34 | + gutter.scrollTop = textarea.scrollTop; |
| 35 | + } |
| 36 | + } |
| 37 | +
|
| 38 | + function handleEditorKeydown(e: KeyboardEvent) { |
| 39 | + if (e.key === 'Tab') { |
| 40 | + e.preventDefault(); |
| 41 | + const ta = e.target as HTMLTextAreaElement; |
| 42 | + const start = ta.selectionStart; |
| 43 | + const end = ta.selectionEnd; |
| 44 | + const value = ta.value; |
| 45 | + scriptDraft = value.substring(0, start) + '\t' + value.substring(end); |
| 46 | + // Restore cursor after tick |
| 47 | + requestAnimationFrame(() => { |
| 48 | + ta.selectionStart = ta.selectionEnd = start + 1; |
| 49 | + }); |
| 50 | + } |
| 51 | + } |
24 | 52 |
|
25 | 53 | let formData = $state({ |
26 | 54 | name: '', |
|
652 | 680 | </div> |
653 | 681 | </div> |
654 | 682 | <div class="script-modal-body"> |
655 | | - <textarea |
656 | | - class="script-modal-textarea" |
657 | | - bind:value={scriptDraft} |
658 | | - placeholder="#!/bin/bash # Commands to run after package installation # Example: # mkdir -p ~/Projects # npm install -g vercel # defaults write com.apple.dock autohide -bool true" |
659 | | - spellcheck="false" |
660 | | - ></textarea> |
| 683 | + <div class="code-editor"> |
| 684 | + <div class="line-gutter" aria-hidden="true"> |
| 685 | + {#each lineNumbers as num} |
| 686 | + <div class="line-number">{num}</div> |
| 687 | + {/each} |
| 688 | + </div> |
| 689 | + <textarea |
| 690 | + class="script-modal-textarea" |
| 691 | + bind:this={scriptTextarea} |
| 692 | + bind:value={scriptDraft} |
| 693 | + placeholder="#!/bin/bash # Commands to run after package installation # Example: # mkdir -p ~/Projects # npm install -g vercel # defaults write com.apple.dock autohide -bool true" |
| 694 | + spellcheck="false" |
| 695 | + onscroll={syncLineScroll} |
| 696 | + onkeydown={handleEditorKeydown} |
| 697 | + ></textarea> |
| 698 | + </div> |
661 | 699 | </div> |
662 | 700 | <div class="script-modal-hint"> |
663 | 701 | Commands run sequentially in your home directory after packages, shell, dotfiles, and macOS preferences are applied. |
|
698 | 736 | /* Header */ |
699 | 737 | .editor-header { |
700 | 738 | position: sticky; |
701 | | - top: 0; |
| 739 | + top: 53px; |
702 | 740 | z-index: 40; |
703 | 741 | display: flex; |
704 | 742 | justify-content: space-between; |
|
0 commit comments