Open the codebase dialog when returning from GitHub

Coming back from GitHub sign-in, cancellation or app installation now
opens the Open codebase dialog automatically with the outcome at the
top (signed in as whoever, access updated, cancelled or failed). A
repository the visitor was trying to open is filled in with Explore
focused, so it is one keypress to continue, instead of loading it
silently in the background. The ?github= parameter is removed from the
address bar and any shared-place fragment is kept.
This commit is contained in:
Andy Dixon
2026-07-18 21:49:30 +01:00
parent eed28b12cd
commit 599ca2235a
3 changed files with 20 additions and 4 deletions
+15 -4
View File
@@ -1258,7 +1258,7 @@ async function uploadLocal(name,chosen){
} }
$('#githubButton').addEventListener('click',startGithub);$('#githubInput').addEventListener('keydown',event=>{if(event.key==='Enter'){event.preventDefault();startGithub();}}); $('#githubButton').addEventListener('click',startGithub);$('#githubInput').addEventListener('keydown',event=>{if(event.key==='Enter'){event.preventDefault();startGithub();}});
async function startGithub(){const url=$('#githubInput').value.trim();$('#githubError').textContent='';if(!/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/?(?:\.git)?$/.test(url)){ $('#githubError').textContent='Enter a GitHub owner/repository URL.';return;}lastGithubUrl=url;dialog.close();showProgress('Cloning repository','Connecting to GitHub…',0,0);try{const response=await apiFetch(apiUrl('/api/jobs'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({kind:'github',url,name:url.split('/').filter(Boolean).pop()?.replace(/\.git$/,'')})});const payload=await response.json();if(!response.ok)throw new Error(payload.error||'Could not start indexing');watchJob(payload.jobId);}catch(error){showError(error.message);}} async function startGithub(){const url=$('#githubInput').value.trim();$('#githubError').textContent='';$('#githubNotice').hidden=true;if(!/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/?(?:\.git)?$/.test(url)){ $('#githubError').textContent='Enter a GitHub owner/repository URL.';return;}lastGithubUrl=url;dialog.close();showProgress('Cloning repository','Connecting to GitHub…',0,0);try{const response=await apiFetch(apiUrl('/api/jobs'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({kind:'github',url,name:url.split('/').filter(Boolean).pop()?.replace(/\.git$/,'')})});const payload=await response.json();if(!response.ok)throw new Error(payload.error||'Could not start indexing');watchJob(payload.jobId);}catch(error){showError(error.message);}}
let activeJobStream=null; let activeJobStream=null;
async function watchJob(jobId){ async function watchJob(jobId){
@@ -1303,13 +1303,24 @@ async function loadGithubStatus(){
if(githubStatus.installUrl)account.append(' · ',Object.assign(document.createElement('a'),{href:githubStatus.installUrl,textContent:'Choose repositories'})); if(githubStatus.installUrl)account.append(' · ',Object.assign(document.createElement('a'),{href:githubStatus.installUrl,textContent:'Choose repositories'}));
}else account.append('Private repository? ',Object.assign(document.createElement('a'),{href:apiUrl('/api/github/login'),textContent:'Sign in with GitHub'})); }else account.append('Private repository? ',Object.assign(document.createElement('a'),{href:apiUrl('/api/github/login'),textContent:'Sign in with GitHub'}));
} }
// Back from GitHub (sign-in, cancel or app installation): open the Open codebase dialog with the
// outcome at the top and any repository the visitor was trying to open ready to go.
async function resumeAfterGithub(){ async function resumeAfterGithub(){
const params=new URLSearchParams(location.search),outcome=params.get('github')||(params.get('setup_action')?'installed':''); const params=new URLSearchParams(location.search),outcome=params.get('github')||(params.get('setup_action')?'installed':'');
if(!outcome)return; if(!outcome)return;
window.history.replaceState(null,'',location.pathname); window.history.replaceState(null,'',location.pathname+location.hash);
let pending='';try{pending=sessionStorage.getItem('codenav.pendingRepo')||'';sessionStorage.removeItem('codenav.pendingRepo');}catch{} let pending='';try{pending=sessionStorage.getItem('codenav.pendingRepo')||'';sessionStorage.removeItem('codenav.pendingRepo');}catch{}
if(outcome==='denied'||outcome==='error'){showError(outcome==='denied'?'GitHub sign-in was cancelled.':'GitHub sign-in failed. Please try again.');return;} const notices={
if(pending){$('#githubInput').value=pending;startGithub();} connected:['ok',githubStatus.login?`Signed in to GitHub as ${githubStatus.login}. Private repos unlocked.`:'Signed in to GitHub.'],
installed:['ok','GitHub access updated. Try that repo again.'],
denied:['warn','GitHub sign-in was cancelled. Public repos still work.'],
error:['error','GitHub sign-in failed. Give it another go.'],
};
const [tone,message]=notices[outcome]||notices.error;
const notice=$('#githubNotice');notice.hidden=false;notice.dataset.tone=tone;notice.textContent=message;
if(pending)$('#githubInput').value=pending;
if(!dialog.open)dialog.showModal();
requestAnimationFrame(()=>(pending&&tone==='ok'?$('#githubButton'):$('#githubInput')).focus());
} }
async function checkBackend(){ async function checkBackend(){
+1
View File
@@ -136,6 +136,7 @@
<div><small>YOUR NEXT PERSPECTIVE</small><h1 id="loadTitle">Open a codebase</h1><p class="dialog-description">See the big picture. Explore every detail.</p></div> <div><small>YOUR NEXT PERSPECTIVE</small><h1 id="loadTitle">Open a codebase</h1><p class="dialog-description">See the big picture. Explore every detail.</p></div>
<button class="close-button" value="cancel" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button> <button class="close-button" value="cancel" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
</div> </div>
<p class="github-notice" id="githubNotice" role="status" hidden></p>
<button type="button" class="source-card" id="localFolderButton"> <button type="button" class="source-card" id="localFolderButton">
<span class="source-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7h7l2-2h4l2 2h3v12H3zM3 10h18"/></svg></span> <span class="source-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7h7l2-2h4l2 2h3v12H3zM3 10h18"/></svg></span>
<span><strong>Local folder</strong><small>Temporarily indexed by this server</small></span> <span><strong>Local folder</strong><small>Temporarily indexed by this server</small></span>
+4
View File
@@ -372,6 +372,10 @@ button.source-card:hover > b { transform: translateX(3px); color: #fff; }
.github-account { grid-column: 1 / -1; margin: 10px 0 0; color: var(--muted); font-size: 11.5px; } .github-account { grid-column: 1 / -1; margin: 10px 0 0; color: var(--muted); font-size: 11.5px; }
.github-account a, .github-account button { padding: 0; border: 0; background: none; color: #cfc8ff; font: inherit; text-decoration: none; border-bottom: 1px solid rgba(207,200,255,.35); cursor: pointer; } .github-account a, .github-account button { padding: 0; border: 0; background: none; color: #cfc8ff; font: inherit; text-decoration: none; border-bottom: 1px solid rgba(207,200,255,.35); cursor: pointer; }
.github-account a:hover, .github-account button:hover { color: #fff; border-bottom-color: #fff; } .github-account a:hover, .github-account button:hover { color: #fff; border-bottom-color: #fff; }
.github-notice { margin: 0 0 4px; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line); background: var(--surface-2); color: var(--text); font-weight: 500; }
.github-notice[data-tone="ok"] { border-color: rgba(54,211,160,.5); background: rgba(54,211,160,.1); }
.github-notice[data-tone="warn"] { border-color: rgba(242,201,76,.5); background: rgba(242,201,76,.1); }
.github-notice[data-tone="error"] { border-color: rgba(255,107,122,.5); background: rgba(255,107,122,.1); }
.form-error { grid-column: 1 / -1; min-height: 16px; margin: 6px 0 -6px; color: var(--danger); font-size: 11px; } .form-error { grid-column: 1 / -1; min-height: 16px; margin: 6px 0 -6px; color: var(--danger); font-size: 11px; }
.dialog-foot { display: flex; gap: 8px; align-items: center; margin: 16px 2px 0; color: var(--dim); font-size: 11px; line-height: 1.6; } .dialog-foot { display: flex; gap: 8px; align-items: center; margin: 16px 2px 0; color: var(--dim); font-size: 11px; line-height: 1.6; }
.dialog-foot::before { content: ""; flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px rgba(54,211,160,.7); } .dialog-foot::before { content: ""; flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px rgba(54,211,160,.7); }