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 <h1>KnotView</h1> 26 <div class="connection-panel"> 27 <input 28 type="text" 29 x-model="serverUrl" 30 placeholder="https://knot.example.com" 31 @keyup.enter="connectToServer" 32 /> 33 <button 34 @click="connectToServer" 35 :disabled="isConnected" 36 x-text="isConnected ? 'Connected ✓' : 'Connect'" 37 ></button> 38 </div> 39 <div 40 x-show="status.message" 41 class="status" 42 :class="status.type" 43 x-text="status.message" 44 ></div> 45 </header> 46 47 <div class="main-content"> 48 <!-- Sidebar --> 49 <aside class="sidebar" x-show="state.currentRepo"> 50 <div> 51 <h2> 52 Repository 53 <button 54 @click="showUsersList" 55 class="secondary" 56 style="padding: 6px 12px; font-size: 12px" 57 > 58 ← Back 59 </button> 60 </h2> 61 <div class="repo-info"> 62 <template x-if="state.currentRepo"> 63 <div> 64 <div class="label">Repository</div> 65 <div 66 class="value" 67 x-text="state.currentRepo?.name" 68 ></div> 69 70 <div class="label">Owner</div> 71 <div 72 class="value" 73 x-text="state.resolvedHandle || state.currentRepo?.did" 74 ></div> 75 76 <div class="label">Clone URL</div> 77 <div class="clone-url"> 78 <code 79 x-text="`${API.getBaseUrl()}/repo/${state.currentRepo?.fullPath}`" 80 ></code> 81 <button 82 class="copy-btn" 83 @click="copyToClipboard(`${API.getBaseUrl()}/repo/${state.currentRepo?.fullPath}`)" 84 > 85 Copy 86 </button> 87 </div> 88 89 <button 90 @click="window.location.href = `${API.getBaseUrl()}/xrpc/sh.tangled.repo.archive?repo=${encodeURIComponent(state.currentRepo?.fullPath)}&branch=${encodeURIComponent(state.currentBranch)}`" 91 style="width: 100%; margin-top: 8px" 92 > 93 Download Archive 94 </button> 95 </div> 96 </template> 97 </div> 98 </div> 99 100 <div class="branches-section"> 101 <h2>Branches</h2> 102 <div class="branch-list"> 103 <template x-for="branch in branches" :key="branch"> 104 <div 105 class="branch-item" 106 :class="{ active: branch.reference.name === state.currentBranch }" 107 @click="switchBranch(branch.reference.name)" 108 > 109 <span x-text="branch.reference.name"></span> 110 </div> 111 </template> 112 </div> 113 </div> 114 </aside> 115 116 <!-- Main Viewer --> 117 <main class="viewer"> 118 <div x-show="loading" class="loading"> 119 <div class="spinner"></div> 120 <p x-text="loadingMessage"></p> 121 </div> 122 123 <div 124 x-show="error && !loading" 125 class="error-message" 126 x-html="error" 127 ></div> 128 129 <!-- Empty State --> 130 <div 131 x-show="!loading && !error && !state.currentRepo && view === 'empty'" 132 class="empty-state" 133 > 134 <svg 135 xmlns="http://www.w3.org/2000/svg" 136 fill="none" 137 viewBox="0 0 24 24" 138 stroke="currentColor" 139 > 140 <path 141 stroke-linecap="round" 142 stroke-linejoin="round" 143 stroke-width="2" 144 d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" 145 /> 146 </svg> 147 <h3>No Repository Selected</h3> 148 <p> 149 Connect to a server and select a repository to 150 browse its contents. 151 </p> 152 </div> 153 154 <!-- Users/Repos List --> 155 <div 156 x-show="!loading && !error && view === 'repoList'" 157 style="padding: 20px" 158 > 159 <template x-for="user in users" :key="user.did"> 160 <div class="user-item"> 161 <div 162 class="user-header" 163 x-text="user.handle || user.did" 164 ></div> 165 <template 166 x-for="repo in user.repos" 167 :key="repo.fullPath" 168 > 169 <div 170 class="repo-item" 171 @click="selectRepository(repo)" 172 > 173 <strong x-text="repo.name"></strong> 174 <small x-text="repo.fullPath"></small> 175 </div> 176 </template> 177 </div> 178 </template> 179 180 <!-- Manual Entry Fallback --> 181 <div 182 x-show="users.length === 0 && !loading" 183 class="empty-state" 184 > 185 <svg 186 xmlns="http://www.w3.org/2000/svg" 187 fill="none" 188 viewBox="0 0 24 24" 189 stroke="currentColor" 190 > 191 <path 192 stroke-linecap="round" 193 stroke-linejoin="round" 194 stroke-width="2" 195 d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" 196 /> 197 </svg> 198 <h3>Repository List Not Available</h3> 199 <p> 200 This server doesn't support automatic repository 201 listing. 202 </p> 203 <p style="margin-top: 20px"> 204 Please enter repository path manually: 205 </p> 206 <div 207 style=" 208 margin-top: 20px; 209 max-width: 400px; 210 margin-left: auto; 211 margin-right: auto; 212 " 213 > 214 <input 215 type="text" 216 x-model="manualRepoPath" 217 placeholder="did:plc:xxx.../repo-name" 218 style=" 219 width: 100%; 220 margin-bottom: 10px; 221 padding: 10px; 222 border: 1px solid #cbd5e1; 223 border-radius: 6px; 224 " 225 @keyup.enter="loadManualRepo" 226 /> 227 <button 228 @click="loadManualRepo" 229 style="width: 100%" 230 > 231 Load Repository 232 </button> 233 </div> 234 </div> 235 </div> 236 237 <!-- File Browser --> 238 <div x-show="!loading && !error && view === 'tree'"> 239 <div class="breadcrumb" x-html="breadcrumbHtml"></div> 240 <div class="file-list" x-html="fileListHtml"></div> 241 <div 242 x-show="readmeHtml" 243 style=" 244 margin-top: 20px; 245 border: 1px solid #e2e8f0; 246 border-radius: 6px; 247 overflow: hidden; 248 " 249 > 250 <div 251 style=" 252 padding: 12px 20px; 253 background: #f8fafc; 254 border-bottom: 1px solid #e2e8f0; 255 font-weight: 600; 256 " 257 > 258 📖 README.md 259 </div> 260 <div 261 class="markdown-content" 262 x-html="readmeHtml" 263 ></div> 264 </div> 265 </div> 266 267 <!-- File Viewer --> 268 <div x-show="!loading && !error && view === 'file'"> 269 <div class="breadcrumb" x-html="breadcrumbHtml"></div> 270 <div class="file-header"> 271 <h3 x-text="currentFile.name"></h3> 272 <div class="file-actions"> 273 <button @click="downloadFile">Download</button> 274 </div> 275 </div> 276 <div 277 x-show="currentFile.isBinary" 278 style=" 279 padding: 40px; 280 text-align: center; 281 color: #64748b; 282 " 283 > 284 <p>Binary file (cannot be displayed)</p> 285 <button 286 @click="downloadFile" 287 style="margin-top: 16px" 288 > 289 Download File 290 </button> 291 </div> 292 <div 293 x-show="currentFile.isMarkdown && !currentFile.isBinary" 294 class="markdown-content" 295 x-html="currentFile.content" 296 ></div> 297 <div 298 x-show="!currentFile.isMarkdown && !currentFile.isBinary" 299 class="file-content" 300 x-html="currentFile.content" 301 ></div> 302 </div> 303 </main> 304 </div> 305 </div> 306 </body> 307</html>