Lexicon-driven ATProto AppView in Gleam — port of HappyView
0

Configure Feed

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

gleamview / priv / public / index.html
9.3 kB 206 lines
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" /> 6 <meta name="color-scheme" content="dark light" /> 7 <title>GleamView</title> 8 <link rel="stylesheet" href="/static/styles.css" /> 9 <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%230c1210'/%3E%3Cpath d='M8 22 L16 8 L24 22' fill='none' stroke='%235eead4' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='20' r='2' fill='%23fbbf24'/%3E%3C/svg%3E" /> 10 </head> 11 <body> 12 <div class="bg-glow" aria-hidden="true"></div> 13 <div id="app" class="shell"> 14 <header class="top"> 15 <div class="brand"> 16 <div class="logo" aria-hidden="true"></div> 17 <div> 18 <h1>GleamView</h1> 19 <p class="tagline">Lexicon-driven ATProto AppView</p> 20 </div> 21 </div> 22 <div class="top-meta"> 23 <span id="status-pill" class="pill muted">connecting…</span> 24 <button type="button" class="btn ghost" id="btn-refresh" title="Refresh"></button> 25 </div> 26 </header> 27 28 <nav class="tabs" role="tablist" aria-label="Main"> 29 <button type="button" class="tab active" data-tab="feed" role="tab" aria-selected="true">Feed</button> 30 <button type="button" class="tab" data-tab="compose" role="tab">Compose</button> 31 <button type="button" class="tab" data-tab="oauth" role="tab">Sign in</button> 32 <button type="button" class="tab" data-tab="lexicons" role="tab">Lexicons</button> 33 <button type="button" class="tab" data-tab="admin" role="tab">Admin</button> 34 <button type="button" class="tab" data-tab="settings" role="tab">Settings</button> 35 </nav> 36 37 <main class="main"> 38 <!-- Feed --> 39 <section id="panel-feed" class="panel active" role="tabpanel"> 40 <div class="panel-head"> 41 <div> 42 <h2>Posts</h2> 43 <p class="muted">Indexed records from <code>com.example.getPosts</code></p> 44 </div> 45 <button type="button" class="btn" id="btn-load-feed">Load feed</button> 46 </div> 47 <div id="feed-empty" class="empty"> 48 Loading feed… 49 </div> 50 <div id="feed-list" class="card-list" hidden></div> 51 </section> 52 53 <!-- Compose --> 54 <section id="panel-compose" class="panel" role="tabpanel" hidden> 55 <div class="panel-head"> 56 <div> 57 <h2>Create post</h2> 58 <p class="muted"> 59 Calls <code>com.example.createPost</code> 60 (<span id="write-mode" class="pill sm"></span> 61 </p> 62 </div> 63 </div> 64 <form id="form-compose" class="stack"> 65 <label> 66 <span>Text</span> 67 <textarea id="compose-text" rows="4" maxlength="1000" placeholder="What's happening on the network?" required></textarea> 68 </label> 69 <label> 70 <span>Author DID / handle <span class="muted">(X-Gleamview-Did)</span></span> 71 <input id="compose-did" type="text" placeholder="did:plc:… or alice.bsky.social" autocomplete="username" /> 72 </label> 73 <div class="row"> 74 <button type="submit" class="btn primary">Publish</button> 75 <span id="compose-status" class="muted"></span> 76 </div> 77 </form> 78 <pre id="compose-result" class="code-block" hidden></pre> 79 </section> 80 81 <!-- OAuth --> 82 <section id="panel-oauth" class="panel" role="tabpanel" hidden> 83 <div class="panel-head"> 84 <div> 85 <h2>ATProto sign-in</h2> 86 <p class="muted">OAuth with PKCE + PAR + DPoP. Session is stored server-side for PDS writes.</p> 87 </div> 88 </div> 89 <form id="form-oauth" class="stack"> 90 <label> 91 <span>Handle</span> 92 <input id="oauth-handle" type="text" placeholder="you.bsky.social" required autocomplete="username" /> 93 </label> 94 <div class="row"> 95 <button type="submit" class="btn primary">Continue with OAuth</button> 96 <button type="button" class="btn ghost" id="btn-oauth-check">Check session</button> 97 <button type="button" class="btn ghost danger" id="btn-oauth-logout">Sign out</button> 98 </div> 99 </form> 100 <div id="oauth-session" class="card" hidden></div> 101 <p class="hint muted"> 102 After login, set <code>ALLOW_LOCAL_WRITES=0</code> on the server and use your DID when composing 103 so procedures proxy to your PDS. 104 </p> 105 </section> 106 107 <!-- Lexicons --> 108 <section id="panel-lexicons" class="panel" role="tabpanel" hidden> 109 <div class="panel-head"> 110 <div> 111 <h2>Lexicon registry</h2> 112 <p class="muted">Requires admin API key (Settings)</p> 113 </div> 114 <button type="button" class="btn" id="btn-load-lexicons">Refresh</button> 115 </div> 116 <div id="lexicon-list" class="card-list"></div> 117 </section> 118 119 <!-- Admin --> 120 <section id="panel-admin" class="panel" role="tabpanel" hidden> 121 <div class="panel-head"> 122 <div> 123 <h2>Admin</h2> 124 <p class="muted">Stats, API clients, records</p> 125 </div> 126 <button type="button" class="btn" id="btn-load-admin">Refresh</button> 127 </div> 128 129 <div class="stats" id="admin-stats"> 130 <div class="stat"><span class="stat-n" id="stat-records"></span><span class="stat-l">records</span></div> 131 <div class="stat"><span class="stat-n" id="stat-lexicons"></span><span class="stat-l">lexicons</span></div> 132 </div> 133 134 <h3>API clients</h3> 135 <form id="form-client" class="inline-form"> 136 <input id="client-name" type="text" placeholder="client name" required /> 137 <button type="submit" class="btn primary">Create client</button> 138 </form> 139 <div id="client-created" class="callout" hidden></div> 140 <div id="client-list" class="card-list"></div> 141 142 <h3>Recent records</h3> 143 <div id="record-list" class="card-list"></div> 144 </section> 145 146 <!-- Settings --> 147 <section id="panel-settings" class="panel" role="tabpanel" hidden> 148 <div class="panel-head"> 149 <div> 150 <h2>Settings</h2> 151 <p class="muted">Optional keys for admin tools. The web UI itself needs none.</p> 152 </div> 153 </div> 154 <form id="form-settings" class="stack"> 155 <label> 156 <span>Admin bearer token <span class="muted">(only for Lexicons / Admin tabs)</span></span> 157 <input id="set-admin-key" type="password" autocomplete="off" placeholder="gva_…" /> 158 </label> 159 <label class="check"> 160 <input id="set-show-admin" type="checkbox" /> 161 <span>Show admin key</span> 162 </label> 163 <label> 164 <span>Default author DID / handle</span> 165 <input id="set-did" type="text" placeholder="did:plc:… or handle" autocomplete="username" /> 166 </label> 167 <details class="muted-card" style="margin-top:0.5rem;padding:0.85rem 1rem"> 168 <summary style="cursor:pointer;font-weight:650">Advanced — third-party API client key</summary> 169 <p class="muted" style="margin:0.5rem 0 0.65rem;font-weight:500"> 170 Only if you call XRPC from another app and want a named client. The built-in web UI does not use this. 171 </p> 172 <label> 173 <span>API client key <code>X-Client-Key</code></span> 174 <input id="set-client-key" type="password" autocomplete="off" placeholder="hvc_…" /> 175 </label> 176 <label class="check"> 177 <input id="set-show-client" type="checkbox" /> 178 <span>Show client key</span> 179 </label> 180 </details> 181 <div class="row"> 182 <button type="submit" class="btn primary">Save</button> 183 <button type="button" class="btn ghost danger" id="btn-clear-settings">Clear all</button> 184 <span id="settings-status" class="muted"></span> 185 </div> 186 </form> 187 <div class="card muted-card"> 188 <h3>Service config</h3> 189 <pre id="config-json" class="code-block">loading…</pre> 190 </div> 191 </section> 192 </main> 193 194 <footer class="foot"> 195 <span>Port of <a href="https://tangled.org/gamesgamesgamesgames.games/happyview" target="_blank" rel="noreferrer">HappyView</a></span> 196 <span class="sep">·</span> 197 <a href="/health">/health</a> 198 <span class="sep">·</span> 199 <a href="/config">/config</a> 200 </footer> 201 </div> 202 203 <div id="toast" class="toast" hidden role="status"></div> 204 <script type="module" src="/static/app.js"></script> 205 </body> 206</html>