mirror your GitHub repos to tangled.org automatically
1

Configure Feed

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

1<script setup lang="ts"> 2import type { ConnectInfo } from '#server/api/connect/info.get' 3 4useSeoMeta({ 5 title: 'Connect · synchub.to', 6 description: 'Connect your tangled identity to your GitHub installation.', 7}) 8 9const route = useRoute() 10 11const installationId = computed(() => { 12 const raw = route.query.installation_id 13 return typeof raw === 'string' && /^\d+$/.test(raw) ? raw : null 14}) 15const verified = computed(() => route.query.verified === '1') 16 17const { data: info } = await useFetch<ConnectInfo>('/api/connect/info', { 18 query: { installationId }, 19 immediate: !!installationId.value, 20 ignoreResponseError: true, 21}) 22 23const login = computed(() => 24 (typeof route.query.login === 'string' ? route.query.login : null) ?? info.value?.login ?? null, 25) 26const accountLabel = computed(() => login.value ?? 'your GitHub account') 27</script> 28 29<template> 30 <div class="shell"> 31 <header class="nav-term"> 32 <nav class="nav-term__line" aria-label="primary"> 33 <span class="prompt" aria-hidden="true">&gt;</span> 34 <a class="nav-term__mark" href="/"><SynchubMark :wordmark="true" :size="18" /></a> 35 </nav> 36 </header> 37 38 <main class="connect"> 39 <div v-if="!installationId" class="panel"> 40 <h1 class="connect__title">No installation to connect</h1> 41 <p class="connect__body"> 42 Start by installing the GitHub App on the repositories you'd like to 43 mirror. 44 </p> 45 <a class="btn btn--primary" href="https://github.com/apps/synchub-to/installations/new">Install the GitHub App</a> 46 </div> 47 48 <div v-else-if="!verified" class="panel"> 49 <p class="connect__eyebrow">step 1 of 2 &middot; verify</p> 50 <h1 class="connect__title">Installed on <span class="connect__login">{{ accountLabel }}</span></h1> 51 <p class="connect__body"> 52 Confirm you administer <strong>{{ accountLabel }}</strong> on GitHub, 53 then you'll pick the tangled handle that mirrors it. We check this so 54 nobody else can bind your repositories to their identity. 55 </p> 56 <a class="btn btn--primary" :href="`/api/github/oauth/start?installationId=${installationId}`"> 57 Verify with GitHub 58 </a> 59 </div> 60 61 <div v-else class="panel"> 62 <p class="connect__eyebrow">step 2 of 2 &middot; connect</p> 63 <h1 class="connect__title">Connect a handle to <span class="connect__login">{{ accountLabel }}</span></h1> 64 <p class="connect__body"> 65 Enter the tangled handle that should mirror 66 <strong>{{ accountLabel }}</strong>. One GitHub account maps to one 67 tangled identity; connecting a new handle replaces any previous one. 68 </p> 69 <form class="signin" action="/api/atproto/login" method="get"> 70 <input type="hidden" name="installationId" :value="installationId"> 71 <label class="signin__label"> 72 <span>handle</span> 73 <span class="signin__field"> 74 <span class="signin__prompt" aria-hidden="true">@</span> 75 <input 76 type="text" 77 name="handle" 78 required 79 placeholder="alice.bsky.social" 80 autocomplete="username" 81 autocapitalize="none" 82 autocorrect="off" 83 spellcheck="false" 84 > 85 </span> 86 </label> 87 <button class="btn btn--primary" type="submit">Connect</button> 88 </form> 89 </div> 90 </main> 91 </div> 92</template> 93 94<style scoped> 95.shell { 96 max-width: 52rem; 97 margin: 0 auto; 98 padding-inline: var(--page-gutter); 99} 100 101.nav-term { 102 padding: var(--space-md) 0; 103 border-bottom: var(--rule-hair) solid var(--color-rule); 104} 105 106.nav-term__line { 107 display: flex; 108 align-items: center; 109 gap: 0.6ch; 110 font-family: var(--font-mono); 111 font-size: var(--text-sm); 112 margin: 0; 113} 114 115.nav-term__line .prompt { color: var(--color-accent); } 116.nav-term__mark { display: inline-flex; text-decoration: none; } 117 118.connect { 119 padding-block: var(--space-2xl) var(--space-xl); 120} 121 122.panel { 123 max-width: 38rem; 124} 125 126.connect__eyebrow { 127 font-family: var(--font-mono); 128 font-size: var(--text-xs); 129 letter-spacing: 0.08em; 130 text-transform: uppercase; 131 color: var(--color-accent-dim); 132 margin: 0 0 var(--space-md); 133} 134 135.connect__title { 136 font-size: var(--text-xl); 137 line-height: 1.1; 138 margin: 0 0 var(--space-md); 139 overflow-wrap: anywhere; 140 min-width: 0; 141} 142 143.connect__login { color: var(--color-accent); } 144 145.connect__body { 146 max-width: var(--measure); 147 color: var(--color-muted); 148 margin: 0 0 var(--space-lg); 149} 150 151.signin { 152 display: flex; 153 flex-wrap: wrap; 154 align-items: end; 155 gap: var(--space-sm); 156} 157 158.signin__label { 159 display: flex; 160 flex-direction: column; 161 gap: var(--space-2xs); 162 font-family: var(--font-mono); 163 font-size: var(--text-xs); 164 letter-spacing: 0.06em; 165 text-transform: uppercase; 166 color: var(--color-neutral); 167} 168 169.signin__field { 170 display: flex; 171 align-items: center; 172 background: var(--color-paper-2); 173 border: var(--rule-hair) solid var(--color-rule-interactive); 174 border-radius: var(--radius-sm); 175} 176 177.signin__field:focus-within { 178 outline: 2px solid var(--color-focus); 179 outline-offset: 2px; 180 border-color: var(--color-accent); 181} 182 183.signin__prompt { 184 padding-inline: 0.75ch 0; 185 color: var(--color-accent); 186 font-family: var(--font-mono); 187} 188 189.signin input:focus-visible { outline: none; } 190 191.signin input { 192 flex: 1; 193 min-width: 14rem; 194 padding: 0.55rem 0.75rem 0.55rem 0.4ch; 195 border: 0; 196 background: transparent; 197 color: var(--color-ink); 198 font-family: var(--font-mono); 199 font-size: var(--text-base); 200} 201 202.signin input::placeholder { color: var(--color-neutral); } 203 204.btn { 205 display: inline-block; 206 padding: 0.55rem 1.1rem; 207 border: var(--rule-hair) solid var(--color-rule-interactive); 208 border-radius: var(--radius-sm); 209 background: var(--color-paper-2); 210 color: var(--color-ink); 211 font-family: var(--font-mono); 212 font-size: var(--text-sm); 213 text-decoration: none; 214 white-space: nowrap; 215 cursor: pointer; 216 transition: transform var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); 217} 218 219.btn:hover { border-color: var(--color-accent); transform: translateY(-1px); } 220.btn:active { transform: translateY(0); } 221.btn--primary { border-color: var(--color-accent); color: var(--color-accent); } 222</style>