update-preview-modal.ts
1 /** 2 * Update Preview Modal 3 * 4 * Shows update summary with accept/reject actions 5 */ 6 7 import { App, Modal, Setting } from 'obsidian'; 8 import { FetchResult } from '../services/git-service'; 9 import { UpdateSummary } from '../services/update-summary-service'; 10 11 export class UpdatePreviewModal extends Modal { 12 private nodeName: string; 13 private updateStatus: FetchResult; 14 private summary: UpdateSummary; 15 private onAccept: () => void; 16 private onReject: () => void; 17 18 constructor( 19 app: App, 20 nodeName: string, 21 updateStatus: FetchResult, 22 summary: UpdateSummary, 23 onAccept: () => void, 24 onReject: () => void 25 ) { 26 super(app); 27 this.nodeName = nodeName; 28 this.updateStatus = updateStatus; 29 this.summary = summary; 30 this.onAccept = onAccept; 31 this.onReject = onReject; 32 } 33 34 onOpen() { 35 const { contentEl } = this; 36 contentEl.empty(); 37 38 console.log('[UpdatePreviewModal] Opening with data:', { 39 nodeName: this.nodeName, 40 commitCount: this.updateStatus.commits.length, 41 commits: this.updateStatus.commits, 42 summary: this.summary 43 }); 44 45 // Title 46 contentEl.createEl('h2', { text: `Updates Available for ${this.nodeName}` }); 47 48 // Overall impact (highlighted) 49 const impactEl = contentEl.createDiv({ cls: 'update-preview-impact' }); 50 impactEl.createEl('strong', { text: this.summary.overallImpact }); 51 52 // Summary section 53 contentEl.createEl('h3', { text: "What's New" }); 54 const userFacingEl = contentEl.createDiv({ cls: 'update-preview-section' }); 55 userFacingEl.createEl('p', { text: this.summary.userFacingChanges }); 56 57 // Technical improvements 58 contentEl.createEl('h3', { text: 'Technical Improvements' }); 59 const technicalEl = contentEl.createDiv({ cls: 'update-preview-section' }); 60 technicalEl.createEl('p', { text: this.summary.technicalImprovements }); 61 62 // Stats 63 contentEl.createEl('h3', { text: 'Update Details' }); 64 const statsEl = contentEl.createDiv({ cls: 'update-preview-stats' }); 65 statsEl.createEl('p', { text: `📊 ${this.updateStatus.commits.length} commits` }); 66 statsEl.createEl('p', { text: `📁 ${this.updateStatus.filesChanged} files changed` }); 67 statsEl.createEl('p', { text: `➕ ${this.updateStatus.insertions} lines added` }); 68 statsEl.createEl('p', { text: `➖ ${this.updateStatus.deletions} lines removed` }); 69 70 // Commit list (collapsible) 71 const commitsEl = contentEl.createDiv({ cls: 'update-preview-commits' }); 72 const detailsEl = commitsEl.createEl('details'); 73 detailsEl.createEl('summary', { text: `View ${this.updateStatus.commits.length} commit${this.updateStatus.commits.length > 1 ? 's' : ''}` }); 74 75 const commitList = detailsEl.createEl('ul'); 76 this.updateStatus.commits.forEach((commit) => { 77 const date = new Date(commit.timestamp * 1000).toLocaleDateString(); 78 const li = commitList.createEl('li'); 79 li.createEl('strong', { text: commit.subject }); 80 li.createEl('br'); 81 li.createEl('span', { 82 text: `${commit.author} • ${date}`, 83 cls: 'update-preview-commit-meta' 84 }); 85 if (commit.body) { 86 li.createEl('br'); 87 // Format commit body: convert bullet points to clean list 88 const formattedBody = this.formatCommitBody(commit.body); 89 li.createEl('span', { text: formattedBody, cls: 'update-preview-commit-body' }); 90 } 91 }); 92 93 // Action buttons 94 const buttonContainer = contentEl.createDiv({ cls: 'modal-button-container' }); 95 96 new Setting(buttonContainer) 97 .addButton((btn) => 98 btn 99 .setButtonText('Apply Update') 100 .setCta() 101 .onClick(() => { 102 console.log('[UpdatePreviewModal] User clicked Apply'); 103 this.close(); 104 this.onAccept(); 105 }) 106 ) 107 .addButton((btn) => 108 btn 109 .setButtonText('Not Now') 110 .onClick(() => { 111 console.log('[UpdatePreviewModal] User clicked Not Now'); 112 this.close(); 113 this.onReject(); 114 }) 115 ); 116 117 // Add custom styles 118 this.addStyles(); 119 } 120 121 onClose() { 122 const { contentEl } = this; 123 contentEl.empty(); 124 } 125 126 /** 127 * Format commit body text for display 128 * Converts bullet points (- item) to clean comma-separated list 129 */ 130 private formatCommitBody(body: string): string { 131 // Split by newlines and filter out empty lines 132 const lines = body.split('\n').map(l => l.trim()).filter(l => l.length > 0); 133 134 // Convert bullet points to clean list 135 const cleaned = lines.map(line => { 136 // Remove leading dash/bullet 137 return line.replace(/^[-*•]\s*/, ''); 138 }); 139 140 // Join with commas for cleaner reading 141 return cleaned.join(', '); 142 } 143 144 private addStyles() { 145 const style = document.createElement('style'); 146 style.textContent = ` 147 .update-preview-impact { 148 background: var(--background-secondary); 149 padding: 1em; 150 border-radius: 8px; 151 margin: 1em 0; 152 font-size: 1.1em; 153 text-align: center; 154 } 155 156 .update-preview-section { 157 margin: 0.5em 0 1.5em 0; 158 line-height: 1.6; 159 } 160 161 .update-preview-stats { 162 display: grid; 163 grid-template-columns: 1fr 1fr; 164 gap: 0.5em; 165 margin: 1em 0; 166 } 167 168 .update-preview-stats p { 169 margin: 0; 170 padding: 0.5em; 171 background: var(--background-secondary); 172 border-radius: 4px; 173 } 174 175 .update-preview-commits { 176 margin: 1em 0 2em 0; 177 } 178 179 .update-preview-commits details { 180 background: var(--background-secondary); 181 padding: 1em; 182 border-radius: 8px; 183 } 184 185 .update-preview-commits summary { 186 cursor: pointer; 187 font-weight: bold; 188 margin-bottom: 0.5em; 189 } 190 191 .update-preview-commits ul { 192 margin-top: 1em; 193 padding-left: 1.5em; 194 } 195 196 .update-preview-commits li { 197 margin: 1em 0; 198 line-height: 1.6; 199 } 200 201 .update-preview-commit-meta { 202 color: var(--text-muted); 203 font-size: 0.9em; 204 } 205 206 .update-preview-commit-body { 207 color: var(--text-muted); 208 font-size: 0.9em; 209 font-style: italic; 210 } 211 212 .modal-button-container { 213 margin-top: 2em; 214 display: flex; 215 justify-content: flex-end; 216 gap: 0.5em; 217 } 218 `; 219 document.head.appendChild(style); 220 } 221 }