A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing
55
fork

Configure Feed

Select the types of activity you want to include in your feed.

Automatically redirect to referring page

Also fixes a bug I just noticed in the Subscribe help page.

authored by

Heath Stewart and committed by tangled.org 13c684c0 ec1b55af

+70 -9
+1 -1
docs/docs/pages/subscribe.mdx
··· 147 147 148 148 The component dispatches custom events you can listen to: 149 149 150 - | Event | Description | `detail` | 150 + | Event | Description | Detail | 151 151 |-------|-------------|----------| 152 152 | `sequoia-subscribed` | Fired when the subscription is created successfully. | `{ publicationUri: string, recordUri: string }` | 153 153 | `sequoia-subscribe-error` | Fired when the subscription fails. | `{ message: string }` |
+69 -8
docs/src/routes/subscribe.ts
··· 35 35 const subscribe = new Hono<{ Bindings: Env }>(); 36 36 37 37 const COLLECTION = "site.standard.graph.subscription"; 38 + const REDIRECT_DELAY_SECONDS = 5; 38 39 39 40 // ============================================================================ 40 41 // Helpers ··· 162 163 ); 163 164 } 164 165 166 + // Prefer an explicit returnTo query param (survives the OAuth round-trip); 167 + // fall back to the Referer header on the first visit, ignoring self-referrals. 168 + const referer = c.req.header("referer"); 169 + const returnTo = 170 + c.req.query("returnTo") ?? 171 + (referer && !referer.includes("/subscribe") ? referer : undefined); 172 + 165 173 const did = getSessionDid(c); 166 174 if (!did) { 167 - return c.html(renderHandleForm(publicationUri, styleHref)); 175 + return c.html(renderHandleForm(publicationUri, styleHref, returnTo)); 168 176 } 169 177 170 178 try { ··· 179 187 ); 180 188 if (existingUri) { 181 189 return c.html( 182 - renderSuccess(publicationUri, existingUri, true, styleHref), 190 + renderSuccess(publicationUri, existingUri, true, styleHref, returnTo), 183 191 ); 184 192 } 185 193 ··· 193 201 }); 194 202 195 203 return c.html( 196 - renderSuccess(publicationUri, result.data.uri, false, styleHref), 204 + renderSuccess( 205 + publicationUri, 206 + result.data.uri, 207 + false, 208 + styleHref, 209 + returnTo, 210 + ), 197 211 ); 198 212 } catch (error) { 199 213 console.error("Subscribe GET error:", error); ··· 202 216 renderHandleForm( 203 217 publicationUri, 204 218 styleHref, 219 + returnTo, 205 220 "Session expired. Please sign in again.", 206 221 ), 207 222 ); ··· 219 234 const body = await c.req.parseBody(); 220 235 const handle = (body["handle"] as string | undefined)?.trim(); 221 236 const publicationUri = body["publicationUri"] as string | undefined; 237 + const formReturnTo = (body["returnTo"] as string | undefined) || undefined; 222 238 223 239 if (!handle || !publicationUri) { 224 240 const styleHref = await getVocsStyleHref(c.env.ASSETS, c.req.url); ··· 228 244 ); 229 245 } 230 246 231 - const returnTo = `${c.env.CLIENT_URL}/subscribe?publicationUri=${encodeURIComponent(publicationUri)}`; 247 + const returnTo = 248 + `${c.env.CLIENT_URL}/subscribe?publicationUri=${encodeURIComponent(publicationUri)}` + 249 + (formReturnTo ? `&returnTo=${encodeURIComponent(formReturnTo)}` : ""); 232 250 setReturnToCookie(c, returnTo, c.env.CLIENT_URL); 233 251 234 252 return c.redirect( ··· 243 261 function renderHandleForm( 244 262 publicationUri: string, 245 263 styleHref: string, 264 + returnTo?: string, 246 265 error?: string, 247 266 ): string { 248 267 const errorHtml = error 249 268 ? `<p class="vocs_Paragraph error">${escapeHtml(error)}</p>` 269 + : ""; 270 + const returnToInput = returnTo 271 + ? `<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />` 250 272 : ""; 251 273 252 274 return page( ··· 255 277 <p class="vocs_Paragraph">Enter your Bluesky handle to subscribe to this publication.</p> 256 278 ${errorHtml} 257 279 <form method="POST" action="/subscribe/login"> 258 - <input type="hidden" name="publicationUri" value="${escapeHtml(publicationUri)}" /> 280 + <input type="hidden" name="publicationUri" value="${escapeHtml(publicationUri)}" /> 281 + ${returnToInput} 259 282 <input 260 283 type="text" 261 284 name="handle" ··· 276 299 recordUri: string, 277 300 existing: boolean, 278 301 styleHref: string, 302 + returnTo?: string, 279 303 ): string { 280 304 const msg = existing 281 305 ? "You're already subscribed to this publication." 282 306 : "You've successfully subscribed!"; 283 307 const escapedPublicationUri = escapeHtml(publicationUri); 284 308 const escapedRecordUri = escapeHtml(recordUri); 309 + 310 + const redirectHtml = returnTo 311 + ? `<p class="vocs_Paragraph" id="redirect-msg">Redirecting to <a class="vocs_Anchor" href="${escapeHtml(returnTo)}">${escapeHtml(returnTo)}</a> in <span id="countdown">${REDIRECT_DELAY_SECONDS}</span>\u00a0seconds\u2026</p> 312 + <script> 313 + (function(){ 314 + var secs = ${REDIRECT_DELAY_SECONDS}; 315 + var el = document.getElementById('countdown'); 316 + var iv = setInterval(function(){ 317 + secs--; 318 + if (el) el.textContent = String(secs); 319 + if (secs <= 0) { clearInterval(iv); location.href = ${JSON.stringify(returnTo)}; } 320 + }, 1000); 321 + })(); 322 + </script>` 323 + : ""; 324 + const headExtra = returnTo 325 + ? `<meta http-equiv="refresh" content="${REDIRECT_DELAY_SECONDS};url=${escapeHtml(returnTo)}" />` 326 + : ""; 327 + 285 328 return page( 286 329 ` 287 330 <h1 class="vocs_H1 vocs_Heading">Subscribed ✓</h1> 288 331 <p class="vocs_Paragraph">${msg}</p> 289 - <p class="vocs_Paragraph"><small>Publication: <code class="vocs_Code"><a href="https://pds.ls/${escapedPublicationUri}">${escapedPublicationUri}</a></code></small></p> 290 - <p class="vocs_Paragraph"><small>Record: <code class="vocs_Code"><a href="https://pds.ls/${escapedRecordUri}">${escapedRecordUri}</a></code></small></p> 332 + ${redirectHtml} 333 + <table class="vocs_Table" style="display:table;table-layout:fixed;width:100%;overflow:hidden;"> 334 + <colgroup><col style="width:7rem;"><col></colgroup> 335 + <tbody> 336 + <tr class="vocs_TableRow"> 337 + <td class="vocs_TableCell">Publication</td> 338 + <td class="vocs_TableCell" style="overflow:hidden;"> 339 + <div style="overflow-x:auto;white-space:nowrap;"><code class="vocs_Code"><a href="https://pds.ls/${escapedPublicationUri}">${escapedPublicationUri}</a></code></div> 340 + </td> 341 + </tr> 342 + <tr class="vocs_TableRow"> 343 + <td class="vocs_TableCell">Record</td> 344 + <td class="vocs_TableCell" style="overflow:hidden;"> 345 + <div style="overflow-x:auto;white-space:nowrap;"><code class="vocs_Code"><a href="https://pds.ls/${escapedRecordUri}">${escapedRecordUri}</a></code></div> 346 + </td> 347 + </tr> 348 + </tbody> 349 + </table> 291 350 `, 292 351 styleHref, 352 + headExtra, 293 353 ); 294 354 } 295 355 ··· 300 360 ); 301 361 } 302 362 303 - function page(body: string, styleHref: string): string { 363 + function page(body: string, styleHref: string, headExtra = ""): string { 304 364 return `<!DOCTYPE html> 305 365 <html lang="en"> 306 366 <head> ··· 309 369 <title>Sequoia · Subscribe</title> 310 370 <link rel="stylesheet" href="${styleHref}" /> 311 371 <script>if(window.matchMedia('(prefers-color-scheme: dark)').matches)document.documentElement.classList.add('dark')</script> 372 + ${headExtra} 312 373 <style> 313 374 .page-container { 314 375 max-width: calc(var(--vocs-content_width, 480px) / 1.6);