1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>KnotView</title> 7 <link rel="icon" type="image/svg+xml" href="favicon.svg" /> 8 <link rel="stylesheet" href="styles.css" /> 9 <link 10 rel="stylesheet" 11 href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" 12 /> 13 <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> 14 <script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script> 15 <script 16 defer 17 src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" 18 ></script> 19 <script src="api.js"></script> 20 <script src="app.js"></script> 21 </head> 22 <body> 23 <div class="container" x-data="app" x-init="init()"> 24 <header> 25 <div class="header-top"> 26 <h1>KnotView</h1> 27 <button class="theme-toggle" @click="toggleTheme"> 28 <span x-show="!darkTheme">🌙</span> 29 <span x-show="darkTheme">☀️</span> 30 <span x-text="darkTheme ? 'Light' : 'Dark'"></span> 31 </button> 32 </div> 33 <div class="connection-panel"> 34 <input 35 type="text" 36 x-model="serverUrl" 37 placeholder="https://knot.example.com" 38 @keyup.enter="connectToServer" 39 /> 40 <button 41 @click="connectToServer" 42 :disabled="isConnected" 43 x-text="isConnected ? 'Connected ✓' : 'Connect'" 44 ></button> 45 </div> 46 <div 47 x-show="status.message" 48 class="status" 49 :class="status.type" 50 x-text="status.message" 51 ></div> 52 </header> 53 54 <div class="main-content"> 55 <!-- Sidebar --> 56 <aside class="sidebar" x-show="state.currentRepo"> 57 <div> 58 <h2> 59 Repository 60 <button 61 @click="showUsersList" 62 class="secondary" 63 style="padding: 6px 12px; font-size: 12px" 64 > 65 ← Back 66 </button> 67 </h2> 68 <div class="repo-info"> 69 <template x-if="state.currentRepo"> 70 <div> 71 <div class="label">Repository</div> 72 <div 73 class="value" 74 x-text="state.currentRepo?.name" 75 ></div> 76 77 <div class="label">Owner</div> 78 <div 79 class="value" 80 x-text="state.resolvedHandle || state.currentRepo?.did" 81 ></div> 82 83 <div class="label">Clone URL</div> 84 <div class="clone-url"> 85 <code 86 x-text="`${API.getBaseUrl()}/repo/${state.currentRepo?.fullPath}`" 87 ></code> 88 <button 89 class="copy-btn" 90 @click="copyToClipboard(`${API.getBaseUrl()}/repo/${state.currentRepo?.fullPath}`)" 91 > 92 Copy 93 </button> 94 </div> 95 96 <button 97 @click="window.location.href = `${API.getBaseUrl()}/xrpc/sh.tangled.repo.archive?repo=${encodeURIComponent(state.currentRepo?.fullPath)}&branch=${encodeURIComponent(state.currentBranch)}`" 98 style="width: 100%; margin-top: 8px" 99 > 100 Download Archive 101 </button> 102 </div> 103 </template> 104 </div> 105 </div> 106 107 <div class="branches-section"> 108 <h2>Branches</h2> 109 <div class="branch-list"> 110 <template x-for="branch in branches" :key="branch"> 111 <div 112 class="branch-item" 113 :class="{ active: branch.reference.name === state.currentBranch }" 114 @click="switchBranch(branch.reference.name)" 115 > 116 <span x-text="branch.reference.name"></span> 117 </div> 118 </template> 119 </div> 120 </div> 121 </aside> 122 123 <!-- Main Viewer --> 124 <main class="viewer"> 125 <div x-show="loading" class="loading"> 126 <div class="spinner"></div> 127 <p x-text="loadingMessage"></p> 128 </div> 129 130 <div 131 x-show="error && !loading" 132 class="error-message" 133 x-html="error" 134 ></div> 135 136 <!-- Empty State --> 137 <div 138 x-show="!loading && !error && !state.currentRepo && view === 'empty'" 139 class="welcome-hero" 140 > 141 <svg 142 xmlns="http://www.w3.org/2000/svg" 143 fill="none" 144 viewBox="0 0 24 24" 145 stroke="currentColor" 146 > 147 <path 148 stroke-linecap="round" 149 stroke-linejoin="round" 150 stroke-width="2" 151 d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" 152 /> 153 </svg> 154 <h2>Welcome to KnotView</h2> 155 <p class="subtitle"> 156 A web-based repository browser for AT Protocol 157 repositories. Browse, explore, and download content 158 from Knot servers. 159 </p> 160 161 <div class="feature-list"> 162 <div class="feature-item"> 163 <h4>🗂️ Browse Repositories</h4> 164 <p> 165 Navigate through files and folders with an 166 intuitive interface 167 </p> 168 </div> 169 <div class="feature-item"> 170 <h4>🌿 Branch Support</h4> 171 <p> 172 Switch between different branches seamlessly 173 </p> 174 </div> 175 <div class="feature-item"> 176 <h4>📄 File Viewer</h4> 177 <p> 178 View files with syntax highlighting and 179 markdown rendering 180 </p> 181 </div> 182 <div class="feature-item"> 183 <h4>📦 Download Archives</h4> 184 <p>Download entire repositories as archives</p> 185 </div> 186 </div> 187 188 <div class="getting-started"> 189 <h3>Getting Started</h3> 190 <ol> 191 <li> 192 Enter your Knot server URL in the input 193 above 194 </li> 195 <li> 196 Click "Connect" to connect to the server 197 </li> 198 <li>Select a repository from the list</li> 199 <li> 200 Browse files, switch branches, and explore! 201 </li> 202 </ol> 203 </div> 204 </div> 205 206 <!-- Users/Repos List --> 207 <div 208 x-show="!loading && !error && view === 'repoList'" 209 style="padding: 20px" 210 > 211 <template x-for="user in users" :key="user.did"> 212 <div class="user-item"> 213 <div 214 class="user-header" 215 x-text="user.handle || user.did" 216 ></div> 217 <template 218 x-for="repo in user.repos" 219 :key="repo.fullPath" 220 > 221 <div 222 class="repo-item" 223 @click="selectRepository(repo)" 224 > 225 <strong x-text="repo.name"></strong> 226 <small x-text="repo.fullPath"></small> 227 </div> 228 </template> 229 </div> 230 </template> 231 232 <!-- Manual Entry Fallback --> 233 <div 234 x-show="users.length === 0 && !loading" 235 class="empty-state" 236 > 237 <svg 238 xmlns="http://www.w3.org/2000/svg" 239 fill="none" 240 viewBox="0 0 24 24" 241 stroke="currentColor" 242 > 243 <path 244 stroke-linecap="round" 245 stroke-linejoin="round" 246 stroke-width="2" 247 d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" 248 /> 249 </svg> 250 <h3>Repository List Not Available</h3> 251 <p> 252 This server doesn't support automatic repository 253 listing. 254 </p> 255 <p style="margin-top: 20px"> 256 Please enter repository path manually: 257 </p> 258 <div 259 style=" 260 margin-top: 20px; 261 max-width: 400px; 262 margin-left: auto; 263 margin-right: auto; 264 " 265 > 266 <input 267 type="text" 268 x-model="manualRepoPath" 269 placeholder="did:plc:xxx.../repo-name" 270 style=" 271 width: 100%; 272 margin-bottom: 10px; 273 padding: 10px; 274 border: 1px solid #cbd5e1; 275 border-radius: 6px; 276 " 277 @keyup.enter="loadManualRepo" 278 /> 279 <button 280 @click="loadManualRepo" 281 style="width: 100%" 282 > 283 Load Repository 284 </button> 285 </div> 286 </div> 287 </div> 288 289 <!-- File Browser --> 290 <div x-show="!loading && !error && view === 'tree'"> 291 <div class="breadcrumb" x-html="breadcrumbHtml"></div> 292 <div class="file-list" x-html="fileListHtml"></div> 293 <div 294 x-show="readmeHtml" 295 style=" 296 margin-top: 20px; 297 border: 1px solid #e2e8f0; 298 border-radius: 6px; 299 overflow: hidden; 300 " 301 > 302 <div 303 style=" 304 padding: 12px 20px; 305 background: #f8fafc; 306 border-bottom: 1px solid #e2e8f0; 307 font-weight: 600; 308 " 309 > 310 📖 README.md 311 </div> 312 <div 313 class="markdown-content" 314 x-html="readmeHtml" 315 ></div> 316 </div> 317 </div> 318 319 <!-- File Viewer --> 320 <div x-show="!loading && !error && view === 'file'"> 321 <div class="breadcrumb" x-html="breadcrumbHtml"></div> 322 <div class="file-header"> 323 <h3 x-text="currentFile.name"></h3> 324 <div class="file-actions"> 325 <button @click="downloadFile">Download</button> 326 </div> 327 </div> 328 <div 329 x-show="currentFile.isBinary" 330 style=" 331 padding: 40px; 332 text-align: center; 333 color: #64748b; 334 " 335 > 336 <p>Binary file (cannot be displayed)</p> 337 <button 338 @click="downloadFile" 339 style="margin-top: 16px" 340 > 341 Download File 342 </button> 343 </div> 344 <div 345 x-show="currentFile.isMarkdown && !currentFile.isBinary" 346 class="markdown-content" 347 x-html="currentFile.content" 348 ></div> 349 <div 350 x-show="!currentFile.isMarkdown && !currentFile.isBinary" 351 class="file-content" 352 x-html="currentFile.content" 353 ></div> 354 </div> 355 </main> 356 </div> 357 </div> 358 </body> 359</html>