feat: unify GitHub sign-in and connect OAuth for gists

This commit is contained in:
tdurieux committed 2026-09-10 18:20:56 +02:00
1 parent 82510628f7
commit 99a50cad7b
16 files changed
+251 -53

No files matched your search

+14 -5
View File
@@ -77,10 +77,15 @@ Do not rerun plaintext cleanup just to enable the App.
## User flow
Sign in offers App and OAuth choices. Both resolve the same existing account by
its GitHub numeric user ID. A signed-in user cannot attach a different GitHub
identity. Legacy accounts without a verified GitHub ID require account recovery;
App login never automatically links by username or email.
Sign in offers one GitHub App button and resolves existing accounts by GitHub
numeric user ID. Signing in preserves existing OAuth credentials and resource
bindings. If the App is disabled, the button uses OAuth instead.
An older account without a GitHub ID receives a one-time recovery prompt. The
user verifies the previous OAuth connection, then resumes App authorization.
Recovery is bound to the expected account and GitHub ID and expires after ten
minutes. A signed-in user cannot attach a different GitHub identity. App login
never automatically links by username or email.
On the anonymization form, **Connect read-only GitHub access** starts user
authorization and then repository installation. **Allow repository access on
@@ -93,7 +98,11 @@ after approval** on the Connections page when approval is delayed.
App-connected accounts default to the App for new repository/PR access. The
explicit **Use existing OAuth access** choice handles repositories not yet
available through the App. An App error never silently selects OAuth. Gists
continue using OAuth in this release.
continue using OAuth. An App-only user entering a gist URL is prompted to connect
OAuth, with the current repository permission scope explained. The form draft
is saved for 30 minutes and restored after authorization. Users with an existing
OAuth connection can use it immediately. OAuth remains available on the
Connections page, separate from sign-in.
**GitHub connections** lists each resource's current connection. First check
read-only access, then switch the resource. The switch validates the existing
+1 -1
View File
@@ -1,6 +1,6 @@
{
"core.min.js": "core.c5bd53363a.min.js",
"vendor.min.js": "vendor.cc9fc42458.min.js",
"vendor.min.js": "vendor.3188bc70a6.min.js",
"mermaid.min.js": "mermaid.f848a72d16.min.js",
"all.min.css": "all.87fb103470.min.css",
"markdown.min.js": "markdown.ad7b1d71c3.min.js",
+1 -1
View File
@@ -130,7 +130,7 @@
"invalid_webhook_signature": "The webhook signature is invalid.",
"webhook_processing_failed": "Webhook processing failed. Retry the delivery.",
"github_app_authorization_cancelled": "GitHub authorization was cancelled.",
"github_account_link_required": "This username belongs to an existing account. Sign in with legacy GitHub OAuth to keep access to your existing anonymizations, then connect the read-only GitHub App from GitHub connections.",
"github_account_link_required": "Verify your existing account with your previous GitHub connection. Future sign-ins will use the GitHub App.",
"github_app_refresh_busy": "GitHub credentials are being renewed. Please retry shortly.",
"github_grant_revocation_failed": "Unable to revoke the GitHub grant. Please retry.",
"another_login_required": "Connect and verify another sign-in method before disconnecting OAuth.",
+6
View File
@@ -24,6 +24,12 @@
<small class="form-text" style="color: var(--ink-muted);">
Repository, pull request (…/pull/42) and gist (gist.github.com/…) URLs are all accepted.
</small>
<aside class="alert alert-info" role="status" v-if="detectedType === 'gist' &amp;&amp; gistOAuthRequired">
<p>Connect GitHub OAuth to access gists. Your account and existing anonymizations stay the same, and your draft will be restored when you return.</p>
<p>GitHub's legacy OAuth connection includes write access to private repositories.</p>
<button type="button" class="btn btn-ink" @click="connectGistOAuth()" v-if="githubConnections?.oauthEnabled">Connect GitHub OAuth to access gists</button>
<p v-else>GitHub OAuth is currently unavailable. Please try again later.</p>
</aside>
<aside class="repo-access-details" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-main">
<div class="repo-access-controls">
+12 -7
View File
@@ -7,8 +7,13 @@
<p class="paper-page-lede">Sign in to create and manage your anonymizations.</p>
</header>
<div class="auth-options">
<p class="auth-existing" role="note" v-if="site_options?.GITHUB_APP_ENABLED &amp;&amp; site_options?.GITHUB_OAUTH_ENABLED">Already have an account? Sign in with <a target="_self" href="/github/login">legacy GitHub OAuth</a> to keep access to your anonymizations. You can connect the read-only App from GitHub connections after signing in.</p>
<section v-if="site_options?.GITHUB_APP_ENABLED">
<section v-if="accountRecovery">
<h2>Verify your existing account</h2>
<p>We found an existing account. Verify it with your previous GitHub connection to keep your anonymizations. Future sign-ins will use the GitHub App.</p>
<a class="btn btn-ink auth-continue" target="_self" href="/github/login?recover=1" v-if="site_options?.GITHUB_OAUTH_ENABLED">Verify with previous GitHub connection</a>
<p v-else>Account verification is currently unavailable. Please try again later.</p>
</section>
<section v-if="site_options?.GITHUB_APP_ENABLED &amp;&amp; !accountRecovery">
<div class="connection-top"><i class="fab fa-github" aria-hidden="true"></i><span class="connection-badge">Read-only access</span></div>
<h2>Continue with GitHub</h2>
<p>Connect through the GitHub App with read-only repository access.</p>
@@ -17,12 +22,12 @@
<li><i class="fas fa-check" aria-hidden="true"></i> No permission to write to your code</li>
</ul>
<a class="btn btn-ink auth-continue" target="_self" href="/github/app/login">Sign in with GitHub <i class="fas fa-arrow-right" aria-hidden="true"></i></a>
<small>Select your repositories after signing in.</small>
<small>Your existing anonymizations and connections stay with your account.</small>
</section>
<section class="auth-legacy" v-if="site_options?.GITHUB_OAUTH_ENABLED">
<div class="connection-title"><h2>Legacy OAuth</h2><span class="connection-badge connection-badge-muted">For gists &amp; existing accounts</span></div>
<p>Use this connection for gists or your existing OAuth setup. GitHub's private repository scope includes write access.</p>
<a class="connection-link" target="_self" href="/github/login">Continue with OAuth <i class="fas fa-arrow-right" aria-hidden="true"></i></a>
<section class="auth-legacy" v-if="!site_options?.GITHUB_APP_ENABLED &amp;&amp; site_options?.GITHUB_OAUTH_ENABLED &amp;&amp; !accountRecovery">
<div class="connection-title"><h2>Continue with GitHub</h2></div>
<p>Sign in to access your account. GitHub OAuth includes write access to private repositories.</p>
<a class="connection-link" target="_self" href="/github/login">Sign in with GitHub <i class="fas fa-arrow-right" aria-hidden="true"></i></a>
</section>
<p role="status" v-if="site_options &amp;&amp; !site_options.GITHUB_APP_ENABLED &amp;&amp; !site_options.GITHUB_OAUTH_ENABLED">GitHub sign-in is currently unavailable. Please try again later.</p>
</div>
+28 -3
View File
@@ -284,7 +284,7 @@ export const homeController = function (state, http, location, window, timeout)
};
// Signed-out visitors cannot open the dashboard; send them to sign in.
state.featureHref = function (f) {
return f.needsUser && !state.user ? "/github/login" : f.href;
return f.needsUser && !state.user ? "/signin" : f.href;
};
state.featureTarget = function (f) {
return f.needsUser && !state.user ? "_self" : f.target || undefined;
@@ -890,10 +890,18 @@ export const anonymizeController = function (state, http, html, params, location
state.sourceUrl = "";
state.githubConnection = undefined;
state.githubConnections = null;
state.grantGitHubAccess = () => {
state.gistOAuthRequired = false;
const saveGitHubDraft = () => {
const draft = {};
for (const key of ["sourceUrl", "terms", "repoId", "pullRequestId", "gistId", "source", "options", "conference", "githubConnection"]) draft[key] = state[key];
sessionStorage.setItem("github-access-draft", JSON.stringify({ path: location.path(), savedAt: Date.now(), draft }));
};
state.connectGistOAuth = () => {
saveGitHubDraft();
window.location.href = "/github/login?returnTo=" + encodeURIComponent(location.path());
};
state.grantGitHubAccess = () => {
saveGitHubDraft();
const returnTo = location.path();
const repository = parseRepoFullName(state.sourceUrl) || "";
const route = state.githubConnections?.appConnected ? "/github/app/install" : "/github/app/login";
@@ -1110,7 +1118,7 @@ export const anonymizeController = function (state, http, html, params, location
state.options.expirationDate = new Date(res.data.options.expirationDate);
}
if (await restoreGitHubDraft()) return;
state.details = (await http.get(`/api/gist/source/${res.data.source.gistId}`)).data;
await getGistDetails();
},
() => { location.url("/404"); }
@@ -1142,6 +1150,7 @@ export const anonymizeController = function (state, http, html, params, location
state.readme = "";
state.html_readme = "";
state.detectedType = null;
state.gistOAuthRequired = false;
let o;
try {
@@ -1449,12 +1458,23 @@ export const anonymizeController = function (state, http, html, params, location
const o = parseGithubUrl(state.sourceUrl);
try {
resetValidity();
state.gistOAuthRequired = false;
if (state.githubConnections?.oauthConnected === false) {
state.gistOAuthRequired = true;
setValidity("sourceUrl", "missing", false);
return;
}
const res = await http.get(`/api/gist/source/${o.gistId}`);
state.details = res.data;
if (!state.gistId) {
state.gistId = "gist-" + o.gistId.substring(0, 6) + "-" + generateRandomId(4);
}
} catch (error) {
if (error.data?.error === "github_oauth_required") {
state.gistOAuthRequired = true;
setValidity("sourceUrl", "missing", false);
return;
}
if (error.data) {
translate("ERRORS." + error.data.error).then((translation) => {
state.addToast({ title: "Error", date: new Date(), body: translation });
@@ -2832,3 +2852,8 @@ export const connectionsController = function (state, http) {
};
state.loadConnections();
};
export const signinController = function (state, http) {
state.accountRecovery = false;
http.get("/github/account-recovery").then(res => { state.accountRecovery = res.data.required; }).catch(() => {});
};
+1 -1
View File
@@ -3,7 +3,7 @@ import * as admin from "./admin.js";
export const pageRoutes = [
{path: "/connections", template: "partials/connections.htm", title: "GitHub connections – Anonymous GitHub", preserveExplorer: false, setup: (state, services) => pages.connectionsController(state, services.http)},
{path: "/signin", template: "partials/signin.htm", title: "Sign in – Anonymous GitHub", preserveExplorer: false, setup: () => {}},
{path: "/signin", template: "partials/signin.htm", title: "Sign in – Anonymous GitHub", preserveExplorer: false, setup: (state, services) => pages.signinController(state, services.http)},
{path: "/", template: "partials/home.htm", title: "Anonymous GitHub – Share the code, not the author", preserveExplorer: false, setup: (state, services) => pages.homeController(state, services.http, services.location, services.window, services.timeout)},
{path: "/dashboard", template: "partials/dashboard.htm", title: "Your anonymizations – Anonymous GitHub", preserveExplorer: false, setup: (state, services) => pages.unifiedDashboardController(state, services.http, services.location, services.promises, services.window, services.quotaService)},
{"path":"/pr-dashboard","redirect":"/dashboard"},
+21 -21
View File
@@ -1,29 +1,29 @@
"use strict";(()=>{var Ul=Object.defineProperty;var zl=(e,t)=>{for(var o in t)Ul(e,o,{get:t[o],enumerable:!0})};var ye={org:"/script/org.f4e2a3f59f.min.js",notebook:"/script/notebook.8844e2735f.min.js",markdown:"/script/markdown.ad7b1d71c3.min.js",editor:"/script/editor.e243722d87.min.js",pdf:"/script/pdf.eaa7573247.min.js"};var rn=new Map,Bl=ye;function Wt(e){if(!rn.has(e)){let t=document.createElement("script");t.src=Bl[e];let o=new Promise((r,n)=>{t.onload=r,t.onerror=()=>{rn.delete(e),t.remove(),n(new Error(`Unable to load ${e}. Please retry.`))}});rn.set(e,o),document.head.appendChild(t)}return rn.get(e)}async function tr(){await Wt("editor"),ace.config.set("basePath","/script/external/ace/")}function ds(e){let t=Object.create(null);for(let o of e.split(","))t[o]=1;return o=>o in t}var Me={},Kt=[],rt=()=>{},Xn=()=>!1,us=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),cs=e=>e.startsWith("onUpdate:"),Ge=Object.assign,Ss=(e,t)=>{let o=e.indexOf(t);o>-1&&e.splice(o,1)},Gl=Object.prototype.hasOwnProperty,Te=(e,t)=>Gl.call(e,t),Ee=Array.isArray,Pt=e=>Ds(e)==="[object Map]",ls=e=>Ds(e)==="[object Set]",sr=e=>Ds(e)==="[object Date]";var _e=e=>typeof e=="function",Ue=e=>typeof e=="string",ct=e=>typeof e=="symbol",Ae=e=>e!==null&&typeof e=="object",Zn=e=>(Ae(e)||_e(e))&&_e(e.then)&&_e(e.catch),rr=Object.prototype.toString,Ds=e=>rr.call(e),xn=e=>Ds(e).slice(8,-1),an=e=>Ds(e)==="[object Object]",ln=e=>Ue(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,ps=ds(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted");var dn=e=>{let t=Object.create(null);return(o=>t[o]||(t[o]=e(o)))},jl=/-\w/g,Ke=dn(e=>e.replace(jl,t=>t.slice(1).toUpperCase())),Wl=/\B([A-Z])/g,qt=dn(e=>e.replace(Wl,"-$1").toLowerCase()),Yt=dn(e=>e.charAt(0).toUpperCase()+e.slice(1)),Rs=dn(e=>e?`on${Yt(e)}`:""),ht=(e,t)=>!Object.is(e,t),Ts=(e,...t)=>{for(let o=0;o<e.length;o++)e[o](...t)},un=(e,t,o,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:o})},eo=e=>{let t=parseFloat(e);return isNaN(t)?e:t};var nr,Os=()=>nr||(nr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Ve(e){if(Ee(e)){let t={};for(let o=0;o<e.length;o++){let r=e[o],n=Ue(r)?Ql(r):Ve(r);if(n)for(let i in n)t[i]=n[i]}return t}else if(Ue(e)||Ae(e))return e}var Kl=/;(?![^(]*\))/g,Yl=/:([^]+)/,Jl=/\/\*[^]*?\*\//g;function Ql(e){let t={};return e.replace(Jl,"").split(Kl).forEach(o=>{if(o){let r=o.split(Yl);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function O(e){let t="";if(Ue(e))t=e;else if(Ee(e))for(let o=0;o<e.length;o++){let r=O(e[o]);r&&(t+=r+" ")}else if(Ae(e))for(let o in e)e[o]&&(t+=o+" ");return t.trim()}var ir="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",ar=ds(ir),Xl=ds(ir+",async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");function cn(e){return!!e||e===""}function Zl(e,t){if(e.length!==t.length)return!1;let o=!0;for(let r=0;o&&r<e.length;r++)o=fs(e[r],t[r]);return o}function or(e,t){if(e.size!==t.size)return!1;let o=Array.from(t),r=new Uint8Array(o.length);for(let n of e){let i=-1;for(let a=0;a<o.length;a++)if(!r[a]&&fs(n,o[a])){i=a;break}if(i<0)return!1;r[i]=1}return!0}function fs(e,t){if(e===t)return!0;let o=sr(e),r=sr(t);if(o||r)return o&&r?e.getTime()===t.getTime():!1;if(o=ct(e),r=ct(t),o||r)return e===t;if(o=Ee(e),r=Ee(t),o||r)return o&&r?Zl(e,t):!1;if(o=Ae(e),r=Ae(t),o||r){if(!o||!r)return!1;if(o=Pt(e),r=Pt(t),o||r||(o=ls(e),r=ls(t),o||r))return o&&r?or(e,t):!1;let n=Object.keys(e).length,i=Object.keys(t).length;if(n!==i)return!1;for(let a in e){let u=e.hasOwnProperty(a),p=t.hasOwnProperty(a);if(u&&!p||!u&&p||!fs(e[a],t[a]))return!1}}return String(e)===String(t)}var lr=e=>!!(e&&e.__v_isRef===!0),c=e=>Ue(e)?e:e==null?"":Ee(e)||Ae(e)&&(e.toString===rr||!_e(e.toString))?lr(e)?c(e.value):JSON.stringify(e,dr,2):String(e),dr=(e,t)=>lr(t)?dr(e,t.value):Pt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((o,[r,n],i)=>(o[Qn(r,i)+" =>"]=n,o),{})}:ls(t)?{[`Set(${t.size})`]:[...t.values()].map(o=>Qn(o))}:ct(t)?Qn(t):Ae(t)&&!Ee(t)&&!an(t)?String(t):t,Qn=(e,t="")=>{var o;return ct(e)?`Symbol(${(o=e.description)!=null?o:t})`:e};function xl(e,...t){console.warn(`[Vue warn] ${e}`,...t)}var Ye,Ps=class{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&Ye&&(Ye.active?(this.parent=Ye,this.index=(Ye.scopes||(Ye.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,o;if(this.scopes){let r=this.scopes.slice();for(t=0,o=r.length;t<o;t++)r[t].pause()}for(t=0,o=this.effects.length;t<o;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!Line truncated
"use strict";(()=>{var zl=Object.defineProperty;var Hl=(e,t)=>{for(var o in t)zl(e,o,{get:t[o],enumerable:!0})};var ye={notebook:"/script/notebook.8844e2735f.min.js",pdf:"/script/pdf.eaa7573247.min.js",markdown:"/script/markdown.ad7b1d71c3.min.js",editor:"/script/editor.e243722d87.min.js",org:"/script/org.f4e2a3f59f.min.js"};var rn=new Map,Gl=ye;function Wt(e){if(!rn.has(e)){let t=document.createElement("script");t.src=Gl[e];let o=new Promise((r,n)=>{t.onload=r,t.onerror=()=>{rn.delete(e),t.remove(),n(new Error(`Unable to load ${e}. Please retry.`))}});rn.set(e,o),document.head.appendChild(t)}return rn.get(e)}async function tr(){await Wt("editor"),ace.config.set("basePath","/script/external/ace/")}function ds(e){let t=Object.create(null);for(let o of e.split(","))t[o]=1;return o=>o in t}var Me={},Kt=[],rt=()=>{},Xn=()=>!1,us=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),cs=e=>e.startsWith("onUpdate:"),Ge=Object.assign,Ss=(e,t)=>{let o=e.indexOf(t);o>-1&&e.splice(o,1)},jl=Object.prototype.hasOwnProperty,Ae=(e,t)=>jl.call(e,t),Ee=Array.isArray,Pt=e=>Ds(e)==="[object Map]",ls=e=>Ds(e)==="[object Set]",sr=e=>Ds(e)==="[object Date]";var _e=e=>typeof e=="function",Fe=e=>typeof e=="string",ct=e=>typeof e=="symbol",Ve=e=>e!==null&&typeof e=="object",Zn=e=>(Ve(e)||_e(e))&&_e(e.then)&&_e(e.catch),rr=Object.prototype.toString,Ds=e=>rr.call(e),xn=e=>Ds(e).slice(8,-1),an=e=>Ds(e)==="[object Object]",ln=e=>Fe(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,ps=ds(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted");var dn=e=>{let t=Object.create(null);return(o=>t[o]||(t[o]=e(o)))},Wl=/-\w/g,Ke=dn(e=>e.replace(Wl,t=>t.slice(1).toUpperCase())),Kl=/\B([A-Z])/g,qt=dn(e=>e.replace(Kl,"-$1").toLowerCase()),Yt=dn(e=>e.charAt(0).toUpperCase()+e.slice(1)),Rs=dn(e=>e?`on${Yt(e)}`:""),ht=(e,t)=>!Object.is(e,t),Ts=(e,...t)=>{for(let o=0;o<e.length;o++)e[o](...t)},un=(e,t,o,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:o})},eo=e=>{let t=parseFloat(e);return isNaN(t)?e:t};var nr,Os=()=>nr||(nr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Pe(e){if(Ee(e)){let t={};for(let o=0;o<e.length;o++){let r=e[o],n=Fe(r)?Xl(r):Pe(r);if(n)for(let i in n)t[i]=n[i]}return t}else if(Fe(e)||Ve(e))return e}var Yl=/;(?![^(]*\))/g,Jl=/:([^]+)/,Ql=/\/\*[^]*?\*\//g;function Xl(e){let t={};return e.replace(Ql,"").split(Yl).forEach(o=>{if(o){let r=o.split(Jl);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function O(e){let t="";if(Fe(e))t=e;else if(Ee(e))for(let o=0;o<e.length;o++){let r=O(e[o]);r&&(t+=r+" ")}else if(Ve(e))for(let o in e)e[o]&&(t+=o+" ");return t.trim()}var ir="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",ar=ds(ir),Zl=ds(ir+",async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");function cn(e){return!!e||e===""}function xl(e,t){if(e.length!==t.length)return!1;let o=!0;for(let r=0;o&&r<e.length;r++)o=fs(e[r],t[r]);return o}function or(e,t){if(e.size!==t.size)return!1;let o=Array.from(t),r=new Uint8Array(o.length);for(let n of e){let i=-1;for(let a=0;a<o.length;a++)if(!r[a]&&fs(n,o[a])){i=a;break}if(i<0)return!1;r[i]=1}return!0}function fs(e,t){if(e===t)return!0;let o=sr(e),r=sr(t);if(o||r)return o&&r?e.getTime()===t.getTime():!1;if(o=ct(e),r=ct(t),o||r)return e===t;if(o=Ee(e),r=Ee(t),o||r)return o&&r?xl(e,t):!1;if(o=Ve(e),r=Ve(t),o||r){if(!o||!r)return!1;if(o=Pt(e),r=Pt(t),o||r||(o=ls(e),r=ls(t),o||r))return o&&r?or(e,t):!1;let n=Object.keys(e).length,i=Object.keys(t).length;if(n!==i)return!1;for(let a in e){let u=e.hasOwnProperty(a),f=t.hasOwnProperty(a);if(u&&!f||!u&&f||!fs(e[a],t[a]))return!1}}return String(e)===String(t)}var lr=e=>!!(e&&e.__v_isRef===!0),c=e=>Fe(e)?e:e==null?"":Ee(e)||Ve(e)&&(e.toString===rr||!_e(e.toString))?lr(e)?c(e.value):JSON.stringify(e,dr,2):String(e),dr=(e,t)=>lr(t)?dr(e,t.value):Pt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((o,[r,n],i)=>(o[Qn(r,i)+" =>"]=n,o),{})}:ls(t)?{[`Set(${t.size})`]:[...t.values()].map(o=>Qn(o))}:ct(t)?Qn(t):Ve(t)&&!Ee(t)&&!an(t)?String(t):t,Qn=(e,t="")=>{var o;return ct(e)?`Symbol(${(o=e.description)!=null?o:t})`:e};function ed(e,...t){console.warn(`[Vue warn] ${e}`,...t)}var Ye,Ps=class{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&Ye&&(Ye.active?(this.parent=Ye,this.index=(Ye.scopes||(Ye.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,o;if(this.scopes){let r=this.scopes.slice();for(t=0,o=r.length;t<o;t++)r[t].pause()}for(t=0,o=this.effects.length;t<o;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!Line truncated
${JSON.stringify(e)}${t?`
while being at
`+JSON.stringify(t):""}`},[Be.NAVIGATION_GUARD_REDIRECT]({from:e,to:t}){return`Redirected from "${e.fullPath}" to "${hc(t)}" via a navigation guard.`},[Be.NAVIGATION_ABORTED]({from:e,to:t}){return`Navigation aborted from "${e.fullPath}" to "${t.fullPath}" via a navigation guard.`},[Be.NAVIGATION_CANCELLED]({from:e,to:t}){return`Navigation cancelled from "${e.fullPath}" to "${t.fullPath}" with a new navigation.`},[Be.NAVIGATION_DUPLICATED]({from:e,to:t}){return`Avoided redundant navigation to current location: "${e.fullPath}".`}};function os(e,t){return Pe(new Error,{type:e,[na]:!0},t)}function Vt(e,t){return e instanceof Error&&na in e&&(t==null||!!(e.type&t))}var mc=["params","query","hash"];function hc(e){if(typeof e=="string")return e;if(e.path!=null)return e.path;let t={};for(let o of mc)o in e&&(t[o]=e[o]);return JSON.stringify(t,null,2)}function oa(e){let t={};if(e===""||e==="?")return t;let o=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;r<o.length;++r){let n=o[r].replace(Gi," "),i=n.indexOf("="),a=_s(i<0?n:n.slice(0,i)),u=i<0?null:_s(n.slice(i+1));if(a in t){let p=t[a];gt(p)||(p=t[a]=[p]),p.push(u)}else t[a]=u}return t}function Go(e){let t="";for(let o in e){let r=e[o];if(o=rc(o),r==null){r!==void 0&&(t+=(t.length?"&":"")+o);continue}(gt(r)?r.map(n=>n&&Mo(n)):[r&&Mo(r)]).forEach(n=>{n!==void 0&&(t+=(t.length?"&":"")+o,n!=null&&(t+="="+n))})}return t}function ra(e){let t={};for(let o in e){let r=e[o];r!==void 0&&(t[o]=gt(r)?r.map(n=>n==null?null:""+n):r==null?r:""+r)}return t}var ia=Symbol(""),jo=Symbol(""),Gn=Symbol(""),jn=Symbol(""),Wn=Symbol("");function Cs(){let e=[];function t(r){return e.push(r),()=>{let n=e.indexOf(r);n>-1&&e.splice(n,1)}}function o(){e=[]}return{add:t,list:()=>e.slice(),reset:o}}function Bt(e,t,o,r,n,i=a=>a()){let a=r&&(r.enterCallbacks[n]=r.enterCallbacks[n]||[]);return()=>new Promise((u,p)=>{let h=m=>{m===!1?p(os(Be.NAVIGATION_ABORTED,{from:o,to:t})):m instanceof Error?p(m):fc(m)?p(os(Be.NAVIGATION_GUARD_REDIRECT,{from:t,to:m})):(a&&r.enterCallbacks[n]===a&&typeof m=="function"&&a.push(m),u())},y=i(()=>e.call(r&&r.instances[n],t,o,h)),g=Promise.resolve(y);e.length<3&&(g=g.then(h)),g.catch(m=>p(m))})}function Kn(e,t,o,r,n=i=>i()){let i=[];for(let a of e)for(let u in a.components){let p=a.components[u];if(!(t!=="beforeRouteEnter"&&!a.instances[u]))if(Hi(p)){let h=(p.__vccOpts||p)[t];h&&i.push(Bt(h,o,r,a,u,n))}else{let h=p();i.push(()=>h.then(y=>{if(!y)throw new Error(`Couldn't resolve component "${u}" at "${a.path}"`);let g=Ju(y)?y.default:y;a.mods[u]=y,a.components[u]=g;let m=(g.__vccOpts||g)[t];return m&&Bt(m,o,r,a,u,n)()}))}}return i}function aa(e,t){let o=[],r=[],n=[],i=Math.max(t.matched.length,e.matched.length);for(let a=0;a<i;a++){let u=t.matched[a];u&&(e.matched.find(h=>ns(h,u))?r.push(u):o.push(u));let p=e.matched[a];p&&(t.matched.find(h=>ns(h,p))||n.push(p))}return[o,r,n]}var vc=()=>location.protocol+"//"+location.host;function ga(e,t){let{pathname:o,search:r,hash:n}=t,i=e.indexOf("#");if(i>-1){let a=n.includes(e.slice(i))?e.slice(i).length:1,u=n.slice(a);return u[0]!=="/"&&(u="/"+u),Uo(u,"")}return Uo(o,e)+r+n}function yc(e,t,o,r){let n=[],i=[],a=null,u=({state:m})=>{let w=ga(e,location),k=o.value,b=t.value,T=0;if(m){if(o.value=w,t.value=m,a&&a===k){a=null;return}T=b?m.position-b.position:0}else r(w);n.forEach(N=>{N(o.value,k,{delta:T,type:Hn.pop,direction:T?T>0?Bn.forward:Bn.back:Bn.unknown})})};function p(){a=o.value}function h(m){n.push(m);let w=()=>{let k=n.indexOf(m);k>-1&&n.splice(k,1)};return i.push(w),w}function y(){if(document.visibilityState==="hidden"){let{history:m}=window;if(!m.state)return;m.replaceState(Pe({},m.state,{scroll:on()}),"")}}function g(){for(let m of i)m();i=[],window.removeEventListener("popstate",u),window.removeEventListener("pagehide",y),document.removeEventListener("visibilitychange",y)}return window.addEventListener("popstate",u),window.addEventListener("pagehide",y),document.addEventListener("visibilitychange",y),{pauseListeners:p,listen:h,destroy:g}}function la(e,t,o,r=!1,n=!1){return{back:e,current:t,forward:o,replaced:r,position:window.history.length,scroll:n?on():null}}function gc(e){let{history:t,location:o}=window,r={value:ga(e,o)},n={value:t.state};n.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(p,h,y){let g=e.indexOf("#"),m=g>-1?(o.host&&document.querySelector("base")?e:e.slice(g))+p:vc()+e+p;try{t[y?"replaceState":"pushState"](h,"",m),n.value=h}catch(w){console.error(w),o[y?"replace":"assign"](m)}}function a(p,h){i(p,Pe({},t.state,la(n.value.back,p,n.value.forward,!0),h,{position:n.value.position}),!0),r.value=p}function u(p,h){let y=Pe({},n.value,t.state,{forward:p,scroll:on()});i(y.current,y,!0),i(p,Pe({},la(r.value,p,null),{position:y.position+1},h),!1),r.value=p}return{location:r,state:n,push:u,replace:a}}function ba(e){e=Zi(e);let t=gc(e),o=yc(e,t.state,t.location,t.replace);function r(i,a=!0){a||o.pauseListeners(),history.go(i)}let n=Pe({locatLine truncated
`)),e.options=Object.assign({},e.options,p.options)})}i();let a=null;e.saveDefault=u=>{u&&u.preventDefault&&u.preventDefault();let p={terms:e.terms.split(`
`).map(h=>h.trim()).filter(h=>h.length>0),options:e.options};e.saving=!0,e.error=null,t.post("/api/user/default",p).then(()=>{i(),e.saving=!1,e.message="Saved",a&&r.cancel(a),a=r(()=>{e.message=null},2500)},h=>{e.saving=!1;let y=h&&h.data&&h.data.error;o("ERRORS."+y).then(g=>{e.error=g},()=>{e.error="Unable to save your defaults. Please try again."})})},e.deleteAccount=()=>{confirm("Delete your account? All your anonymized repositories, gists, and pull requests will be removed, and your personal data will be erased. This cannot be undone.")&&(e.deletingAccount=!0,t.delete("/api/user").then(()=>{window.location.href="/"},()=>{e.deletingAccount=!1,e.deleteError="Unable to delete the account. Please try again."}))}},Ra=function(e,t,o){e.repoId=null,e.repoUrl=null,e.claim=()=>{t.post("/api/repo/claim",{repoId:e.repoId,repoUrl:e.repoUrl}).then(r=>{o.url("/dashboard")},r=>{e.error=r.data,e.claimForm.repoUrl.setValidity("not_found",!1),e.claimForm.repoId.setValidity("not_found",!1)})}},Ta=function(e,t,o,r,n){e.user&&!e.user.status&&o.url("/dashboard"),e.watch("user.status",()=>{e.user&&!e.user.status&&o.url("/dashboard")}),e.features=[{key:"anonymize",num:"01",eyebrow:"Anonymize",title:"Double-anonymous,",accent:"your rules.",text:"Choose what reviewers may see: links, images, PDFs, notebooks, GitHub Pages. Add your own terms, with regex if you need it, and pick the expiration date.",cta:"Start an anonymization",href:"/anonymize",url:"anonymous.4open.science/anonymize",img:"/imgs/anonymize.png",alt:"The anonymize form: source repository, terms to redact, options and a live README preview"},{key:"review",num:"02",eyebrow:"Review",title:"Reviewers browse",accent:"the real thing.",text:"Highlighted source code, rendered PDFs, images, and notebooks, in a familiar file explorer. GitHub Pages is also supported.",cta:"Open the example",href:"https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/",target:"_self",url:"anonymous.4open.science/r/840c8c57-\u2026",img:"/imgs/explorer.png",alt:"The repository explorer with a file tree and a rendered README"},{key:"manage",num:"03",eyebrow:"Manage",title:"One dashboard,",accent:"until the decision.",text:"Monitor views, edit configuration, remove or update your repository. Program chairs can group submissions under a conference with one shared expiry.",cta:"Open the dashboard",href:"/dashboard",needsUser:!0,url:"anonymous.4open.science/dashboard",img:"/imgs/dashboard.png",alt:"The dashboard listing anonymized repositories with status, views and expiry"}],e.feature=e.features[0].key,e.selectFeature=function(u){e.feature=u},e.featureHref=function(u){return u.needsUser&&!e.user?"/github/login":u.href},e.featureTarget=function(u){return u.needsUser&&!e.user?"_self":u.target||void 0},e.featureKeydown=function(u,p){let h={ArrowDown:1,ArrowRight:1,ArrowUp:-1,ArrowLeft:-1,Home:"first",End:"last"}[u.key];if(h===void 0)return;u.preventDefault();let y=e.features.length,g=h==="first"?0:h==="last"?y-1:(p+h+y)%y;e.feature=e.features[g].key,n(()=>{let m=r.document.getElementById("feature-tab-"+e.feature);m&&m.focus()})},e.cards=[{key:"repositories",total:0,label:"repositories anonymized"},{key:"users",total:0,label:"researchers"},{key:"pageViews",total:0,label:"page views"},{key:"pullRequests",total:0,label:"pull requests"}];function i(){t.get("/api/stat/").then(u=>{e.stat=u.data,e.cards[0].total=u.data.nbRepositories,e.cards[1].total=u.data.nbUsers,e.cards[2].total=u.data.nbPageViews,e.cards[3].total=u.data.nbPullRequests})}i();function a(u){let p={series:u,bars:[],viewW:100,deltaToday:0,pctChange:0,pctAbs:0,isUp:!0};if(!u||u.length<2)return p;let h=new Array(u.length-1);for(let b=1;b<u.length;b++)h[b-1]=u[b]-u[b-1];let y=h.length,g=Math.max.apply(null,h),m=Math.min.apply(null,h),w=Math.min(0,m),k=g-w||1;p.viewW=y*2,p.bars=new Array(y);for(let b=0;b<y;b++){let T=(h[b]-w)/k,N=Math.max(1.5,T*34);p.bars[b]={x:(b*2+.25).toFixed(2),y:(36-N).toFixed(2),w:"1.5",h:N.toFixed(2)}}if(p.deltaToday=h[y-1],y>=2){let b=h[y-2];b&&(p.pctChange=(p.deltaToday-b)/b*100)}return p.pctAbs=Math.round(Math.abs(p.pctChange)),p.isUp=p.pctChange>=0,p}e.history={repositories:a([]),users:a([]),pageViews:a([]),pullRequests:a([])},t.get("/api/stat/history?days=60").then(u=>{let p=u.data||[];e.history={repositories:a(p.map(h=>h.nbRepositories||0)),users:a(p.map(h=>h.nbUsers||0)),pageViews:a(p.map(h=>h.nbPageViews||0)),pullRequests:a(p.map(h=>h.nbPullRequests||0))}})},Oa=function(e,t,o,r,n,i){let a=mt();e.on("routeLeave",function(){$('[data-toggle="tooltip"]').tooltip("dispose")}),e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),a.timeout(()=>{$('[data-toggle="tooltip"]').tooltip()},250),e.items=[],e.search="",e.loading=!0,e.statusKeyLabels={ready:"Ready",progress:"In progress",error:"Error",expired:"Expired",removed:"Removed"};let u=["queue","download","downloaded","preparing","anonymizing"];function p(C){return C==="ready"||C==="error"?C:C==="expired"||C==="expiring"?"expired" Line truncated
`)),e.options=Object.assign({},e.options,H.options),e.options.expirationDate=H.options&&H.options.expirationDate?new Date(H.options.expirationDate):u(),D&&D()})}function g(D,V,H){e.anonymize&&e.anonymize[D]&&e.anonymize[D].setValidity(V,H)}function m(D){try{let V=parseGithubUrl(D);if(V&&V.owner&&V.repo)return V.owner+"/"+V.repo}catch{}return null}function w(){return!e.isUpdate||!e._originalRepositoryID?void 0:m(e.sourceUrl)===e._originalFullName?e._originalRepositoryID:void 0}async function k(){e._preservingDraft=!0;try{await e.urlSelected(!0)}finally{e._preservingDraft=!1}}async function b(){let D;try{D=JSON.parse(sessionStorage.getItem("github-access-draft")||"null")}catch{return!1}if(!D||D.path!==n.path()||Date.now()-D.savedAt>=30*6e4)return!1;for(let V of["sourceUrl","terms","repoId","pullRequestId","gistId","source","options","conference","githubConnection"])Object.prototype.hasOwnProperty.call(D.draft,V)&&(e[V]=D.draft[V]);return e.options.expirationDate&&(e.options.expirationDate=new Date(e.options.expirationDate)),await k(),sessionStorage.removeItem("github-access-draft"),!0}y(()=>{!r.repoId&&!r.pullRequestId&&!r.gistId&&a(b,0),r.repoId&&r.repoId!=""&&(e.isUpdate=!0,e.detectedType="repo",e.repoId=r.repoId,t.get("/api/repo/"+e.repoId).then(async D=>{e.githubConnection=D.data.connection||"oauth",e.sourceUrl="https://github.com/"+D.data.source.fullName,e._originalFullName=D.data.source.fullName,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.role=D.data.role||"owner",e.coauthors=D.data.coauthors||[],e._originalBranch=D.data.source.branch,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,e.repositoryID=D.data.source.repositoryID,e._originalRepositoryID=D.data.source.repositoryID,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await b()&&(await Promise.all([T(),N()]),A())},()=>{n.url("/404")})),r.pullRequestId&&r.pullRequestId!=""&&(e.isUpdate=!0,e.detectedType="pr",e.pullRequestId=r.pullRequestId,t.get("/api/pr/"+e.pullRequestId).then(async D=>{if(e.githubConnection=D.data.connection||"oauth",e.sourceUrl="https://github.com/"+D.data.source.repositoryFullName+"/pull/"+D.data.source.pullRequestId,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await b())try{e.details=(await t.get(`/api/pr/${D.data.source.repositoryFullName}/${D.data.source.pullRequestId}`,{params:{connection:e.githubConnection}})).data}catch(V){let H=V&&V.data&&V.data.error;H&&(i("ERRORS."+H).then(se=>{e.addToast({title:"Error",date:new Date,body:se}),e.error=se},console.error),Se(H))}},()=>{n.url("/404")})),r.gistId&&r.gistId!=""&&(e.isUpdate=!0,e.detectedType="gist",e.gistId=r.gistId,t.get("/api/gist/"+e.gistId).then(async D=>{e.sourceUrl="https://gist.github.com/"+D.data.source.gistId,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await b()&&(e.details=(await t.get(`/api/gist/source/${D.data.source.gistId}`)).data)},()=>{n.url("/404")}))}),t.get("/github/connections").then(D=>{e.githubConnections=D.data,!r.repoId&&!r.pullRequestId&&!r.gistId&&e.githubConnection===void 0&&D.data.appEnabled&&D.data.appConnected&&(e.githubConnection="github-app")}).catch(()=>{}),e.urlSelected=async(D=!1)=>{D||(e.terms=e.defaultTerms),!D&&!e.isUpdate&&(e.repoId="",e.pullRequestId="",e.gistId=""),e.details=null,e.branches=[],D||(e.source={type:"GitHubStream",branch:"",commit:""}),e.anonymize_readme="",e.readme="",e.html_readme="",e.detectedType=null;let V;try{V=parseGithubUrl(e.sourceUrl)}catch{g("sourceUrl","github",!1);return}g("sourceUrl","github",!0);try{V.gistId&&!V.repo?(e.detectedType="gist",e.source={gistId:V.gistId},await ue()):V.pullRequestId?(e.detectedType="pr",e.source={repositoryFullName:V.owner+"/"+V.repo,pullRequestId:V.pullRequestId},await q()):(e.detectedType="repo",await Promise.all([T(),N()]),A())}catch{return}$('[data-toggle="tooltip"]').tooltip()},$('[data-toggle="tooltip"]').tooltip(),e.watch("source.branch",async()=>{if(e.detectedType!=="repo")return;let D=e.branches.filter(H=>H.name==e.source.branch)[0];if(!D)return;!(e.isUpdate&&e._originalBranch===e.source.branch&&!!e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=D.commit),e.readme=D.readme,await N(),A()}),e.getBranches=async D=>{let V=parseGithubUrl(e.sourceUrl);try{let H=await t.get(`/api/repo/${V.owner}/${V.repo}/branches`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&m(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:D===!0?"1":"0",repositoryID:w()}});e.branches=H.data,e.sourceUnreachable=!1,e.source.branch||(e.source.branch=e.details.defaultBranch);let se=e.branches.filter(ie=>ie.name==e.source.branch);se.length>0&&(!(!D&&e.isUpdate&&!e.options.update&&e._originalBranch===e.source.branch&&e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=se[0].commit),e.readme=se[0].readme,await N(D))}catch(H){e.branches=[],e.sourceUnreachable=H&&(H.status===404||H.data&&H.data.error==="repo_not_found");let se=H&&H.data&&H.data.error||(H&&H.status===404?"repo_not_found":"unknown_error");i("ERRORS."+se).then(ie=>{e.toasts=e.toasts||[],e.addToast({title:"Error",date:new Date,body:ie}),e.error=ie},console.error),typeof g=="function"&&g("sourceUrl","missing",!1)}};async function T(){let D=parseGithubUrl(e.sourceUrl);try{le();let V=await t.get(`/api/repo/${D.owner}/${D.repo}/`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&m(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,repositoryID:w(),force:"1"}});e.details=V.data,e.details&&e.details.id&&(e.repositoryID=e.details.id),e.repoId||(e.repoId=e.details.repo+"-"+generateRandomId(4)),await e.getBranches()}catch(V){throw V.data&&(i("ERRORS."+V.data.error).then(H=>{e.addToast({title:"Error",date:new Date,body:H}),e.error=H},console.error),Se(V.data.error)),g("sourceUrl","missing",!1),V}}async function N(D){if(e.readme&&!D)return e.readme;let V=parseGithubUrl(e.sourceUrl);try{let H=await t.get(`/api/repo/${V.owner}/${V.repo}/readme`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&m(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:D===!0?"1":"0",branch:e.source.branch,repositoryID:w()}});e.readme=H.data}catch{e.readme=""}}function R(){let D={terms:e.terms?e.terms.split(`
`):[],image:!!e.options.image,link:!!e.options.link,repoId:e.repoId};e.source&&e.source.branch&&(D.branchName=e.source.branch);try{let V=parseGithubUrl(e.sourceUrl);D.repoName=`${V.owner}/${V.repo}`}catch{}return D}function I(D,V){let H=null,se=0;return function(){H&&a.cancel(H),H=a(()=>{H=null;let qe=++se,L=D();L&&t.post("/api/anonymize-preview",L).then(ne=>{qe===se&&V(ne.data)},()=>{})},200)}}let C=I(()=>e.readme?{content:e.readme,options:R()}:null,D=>{e.anonymize_readme=D.content||"";let V="";try{let se=parseGithubUrl(e.sourceUrl),ie=e.source.branch||e.details&&e.details.defaultBranch||"main";V=`https://github.com/${se.owner}/${se.repo}/raw/${ie}/`}catch{}let H=renderMD(e.anonymize_readme,V);e.html_readme=H,a(Prism.highlightAll,150)});function A(){!e.anonymize||!e.anonymize.terms||(e.termsRegexWarning=!!e.terms&&!!e.terms.match(/[-[\]{}()*+?.,\\^$|#]/g),C())}async function q(){let D=parseGithubUrl(e.sourceUrl);try{le();let V=await t.get(`/api/pr/${D.owner}/${D.repo}/${D.pullRequestId}`,{params:{connection:e.githubConnection}});e.details=V.data,e.pullRequestId||(e.pullRequestId=D.repo+"-PR"+D.pullRequestId+"-"+generateRandomId(4))}catch(V){throw V.data&&(i("ERRORS."+V.data.error).then(H=>{e.addToast({title:"Error",date:new Date,body:H}),e.error=H},console.error),Se(V.data.error)),g("sourceUrl","missing",!1),V}}let x=ze(new Map),X=new Set;function K(){let D=new Set,V=e.details&&e.details.pullRequest;if(!V)return D;typeof V.title=="string"&&D.add(V.title),typeof V.body=="string"&&D.add(V.body),typeof V.diff=="string"&&D.add(V.diff);let H=V.comments||[];for(let se of H)typeof se.author=="string"&&D.add(se.author),typeof se.body=="string"&&D.add(se.body);return D}let U=I(()=>{let D=K();X=D;let V=Array.from(D);return V.length===0?null:{contents:V,options:R()}},D=>{if(!D||!Array.isArray(D.contents))return;let V=Array.from(X),H=new Map;for(let se=0;se<V.length&&se<D.contents.length;se++)H.set(V[se],D.contents[se]);x.clear(),H.forEach((se,ie)=>x.set(ie,se))});e.anonymizePrContent=function(D){return D&&(x.has(D)?x.get(D):(X.has(D)||U(),D))};async function ue(){let D=parseGithubUrl(e.sourceUrl);try{le();let V=await t.get(`/api/gist/source/${D.gistId}`);e.details=V.data,e.gistId||(e.gistId="gist-"+D.gistId.substring(0,6)+"-"+generateRandomId(4))}catch(V){throw V.data&&(i("ERRORS."+V.data.error).then(H=>{e.addToast({title:"Error",date:new Date,body:H}),e.error=H},console.error),Se(V.data.error)),g("sourceUrl","missing",!1),V}}let te=ze(new Map),re=new Set;function pe(){let D=new Set,V=e.details&&e.details.gist;if(!V)return D;typeof V.description=="string"&&D.add(V.description),typeof V.ownerLogin=="string"&&D.add(V.ownerLogin);let H=V.files||[];for(let ie of H)typeof ie.filename=="string"&&D.add(ie.filename),typeof ie.content=="string"&&D.add(ie.content);let se=V.comments||[];for(let ie of se)typeof ie.author=="string"&&D.add(ie.author),typeof ie.body=="string"&&D.add(ie.body);return D}let me=I(()=>{let D=pe();re=D;let V=Array.from(D);return V.length===0?null:{contents:V,options:R()}},D=>{if(!D||!Array.isArray(D.contents))return;let V=Array.from(re),H=new Map;for(let se=0;se<V.length&&se<D.contents.length;se++)H.set(V[se],D.contents[se]);te.clear(),H.forEach((se,ie)=>te.set(ie,se)),be()});e.anonymizeGistContent=function(D){return D&&(te.has(D)?te.get(D):(re.has(D)||me(),D))},e.previewGistFiles=[];function be(){let D=e.details&&e.details.gist&&e.details.gist.files||[];e.previewGistFiles=D.map(V=>({filename:e.anonymizeGistContent(V.filename),content:e.anonymizeGistContent(V.content),language:V.language}))}e.watch("details.gist.files",be,!0),e.watch("terms",be);function he(){let D=e.conference;if(e.conference_data=null,!D)return;let V=e.isUpdate&&D===e._originalConference;t.get("/api/conferences/"+D).then(H=>{e.conference===D&&(e.conference_data=H.data,e.conference_data.startDate=new Date(e.conference_data.startDate),e.conference_data.endDate=new Date(e.conference_data.endDate),!V&&(e.options.expirationDate=new Date(e.conference_data.endDate),e.options.expirationMode="remove",e.options.update=e.conference_data.options.update,e.options.image=e.conference_data.options.image,e.options.pdf=e.conference_data.options.pdf,e.options.notebook=e.conference_data.options.notebook,e.options.link=e.conference_data.options.link))},()=>{e.conference===D&&(e.conference_data=null)})}function le(){g("repoId","used",!0),g("repoId","format",!0),g("pullRequestId","used",!0),g("pullRequestId","format",!0),g("gistId","used",!0),g("gistId","format",!0),g("sourceUrl","used",!0),g("sourceUrl","missing",!0),g("sourceUrl","access",!0),g("sourceUrl","github",!0),g("commit","exists",!0),g("conference","activated",!0),g("terms","format",!0),e.termsRegexWarning=!1}function Ie(){let D=e.anonymize&&e.anonymize.expirationDate;return!e.options.expirationDate||D&&D.invalid?(D&&D.setDirty&&D.setDirty(),e.error="Please choose a valid expiration date.",!0):!1}function Se(D){let V=e.detectedType==="pr"?"pullRequestId":e.detectedType==="gist"?"gistId":"repoId"; Line truncated
`).filter(ie=>ie),connection:e.githubConnection,fullName:`${V.owner}/${V.repo}`,repository:e.sourceUrl,options:e.options,source:e.source,conference:e.conference};e.details&&(H.options.pageSource=e.details.pageSource),le();let se=e.isUpdate?"/api/repo/"+e.repoId:"/api/repo/";t.post(se,H,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/status/"+e.repoId},ie=>{ie.data&&(i("ERRORS."+ie.data.error).then(qe=>{e.error=qe},console.error),Se(ie.data.error))}).finally(()=>{D.target.disabled=!1})},e.anonymizeGist=D=>{if(Ie())return;D.target.disabled=!0;let V=parseGithubUrl(e.sourceUrl),H={gistId:e.gistId,terms:e.terms.trim().split(`
`).filter(ie=>ie),source:{gistId:V.gistId},options:e.options,conference:e.conference};le();let se=e.isUpdate?"/api/gist/"+e.gistId:"/api/gist/";t.post(se,H,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/gist/"+e.gistId},ie=>{ie.data&&(i("ERRORS."+ie.data.error).then(qe=>{e.error=qe},console.error),Se(ie.data.error))}).finally(()=>{D.target.disabled=!1})},e.anonymizePullRequest=D=>{if(Ie())return;D.target.disabled=!0;let V=parseGithubUrl(e.sourceUrl),H={pullRequestId:e.pullRequestId,connection:e.githubConnection,terms:e.terms.trim().split(`
`).filter(ie=>ie),source:{repositoryFullName:`${V.owner}/${V.repo}`,pullRequestId:V.pullRequestId},options:e.options,conference:e.conference};le();let se=e.isUpdate?"/api/pr/"+e.pullRequestId:"/api/pr/";t.post(se,H,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/pr/"+e.pullRequestId},ie=>{ie.data&&(i("ERRORS."+ie.data.error).then(qe=>{e.error=qe},console.error),Se(ie.data.error))}).finally(()=>{D.target.disabled=!1})},e.watch("conference",()=>{he()}),e.watch("terms",()=>{e.detectedType==="repo"&&A(),e.detectedType==="pr"&&U(),e.detectedType==="gist"&&me()}),e.watch("options.image",()=>{e.detectedType==="repo"&&A(),e.detectedType==="pr"&&U(),e.detectedType==="gist"&&me()}),e.watch("options.link",()=>{e.detectedType==="repo"&&A(),e.detectedType==="pr"&&U(),e.detectedType==="gist"&&me()}),e.watch("details",()=>{e.detectedType==="pr"&&U(),e.detectedType==="gist"&&me()},!0)},Yo=function(e,t,o,r,n,i){let a=mt(),u=as(),p=0,h=!1;e.on("dispose",()=>{h=!0,p++,y&&y.resolve()}),e.files=[],e.isMac=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),e.fileSearchQuery="",e.fileSearchResults=null,e.fileSearchLoading=!1,u(document,"keydown",function(U){if((U.metaKey||U.ctrlKey)&&U.key==="k"){U.preventDefault();var ue=document.querySelector(".tree-search-input");ue&&(ue.focus(),ue.select())}});var y=null;e.onFileSearchChange=function(){y&&(y.resolve(),y=null);let U=e.fileSearchQuery;if(!U||U.length<2){e.fileSearchResults=null,e.fileSearchLoading=!1;return}e.fileSearchLoading=!0;let ue=i.defer();y=ue,t.get(`/api/repo/${e.repoId}/files/search?q=${encodeURIComponent(U)}`,{timeout:ue.promise}).then(function(te){if(!(h||y!==ue)){y=null,e.fileSearchLoading=!1;var re={};e.files.forEach(function(L){re[(L.path||"")+"/"+L.name]=!0});for(var pe=[],me={},be=0;be<te.data.length;be++){var he=te.data[be];if(he.path)for(var le=he.path.split("/"),Ie="",Se=0;Se<le.length;Se++){var D=Ie;Ie=Ie?Ie+"/"+le[Se]:le[Se];var V=D+"/"+le[Se];!re[V]&&!me[V]&&(me[V]=!0,pe.push({name:le[Se],path:D}))}}pe.sort(function(L,ne){return(L.path||"").split("/").length-(ne.path||"").split("/").length}),pe.length>0&&e.files.push.apply(e.files,pe);for(var H=[],se=0;se<te.data.length;se++){var ie=te.data[se],qe=(ie.path||"")+"/"+ie.name;!re[qe]&&ie.size!=null&&(H.push(ie),re[qe]=!0)}H.length>0&&e.files.push.apply(e.files,H),e.fileSearchResults=te.data}},function(){!h&&y===ue&&(y=null,e.fileSearchLoading=!1,e.fileSearchResults=[])})};let g={yml:"yaml",txt:"text",py:"python",js:"javascript",ts:"typescript"},m=["license","txt"],w=["png","jpg","jpeg","gif","svg","ico","bmp","tiff","tif","webp","avif","heif","heic"],k=["wav","mp3","ogg","wma","flac","aac","m4a"],b=["mp4","avi","webm","mov","mpg","mpeg","mkv","flv","wmv","3gp","3g2","m4v","f4v","f4p","f4a","f4b"];e.on("routeUpdate",function(U,ue){if(e.repoId!=r.repoId)return K();if((r.path||"")!=e.filePath){e.filePath=r.path||"",e.paths=e.filePath.split("/").filter(te=>te&&te.trim().length>0),X();for(let te=0;te<e.paths.length-1;te++){let re=te>0?e.paths.slice(0,te).join("/"):"";e.files.some(me=>me.path===re)||e.getFiles(re)}}});function T(){if(e.paths[0]!="")return;let U=["readme.md","readme.txt","readme.org","readme.1st","readme"],ue={};for(let re of e.files)re.name.toLowerCase().indexOf("readme")>-1&&(ue[re.name.toLowerCase()]=re.name);let te=null;for(let re of U)if(ue[re]){te=re;break}if(!te&&Object.keys(ue).length>0&&(te=Object.keys(ue)[0]),te){let re=o.url();re[re.length-1]!="/"&&(re+="/"),o.url(re+encodePathForUrl(ue[te]))}}e.fileCounts=null,e.getFiles=function(U){let ue=e.repoId;return t.get(`/api/repo/${e.repoId}/files/?path=${encodeURIComponent(U)}&v=${e.options.lastUpdateDate}`).then(function(te){if(h||ue!==e.repoId)return[];let re=U||"";return e.files=e.files.filter(pe=>pe.path!==re),e.files.push(...te.data),te.data},function(te){if(h||ue!==e.repoId)return[];e.type="error",e.content=te&&te.data&&te.data.error||"unknown_error",e.files=[]})};function N(){let U=e.repoId;t.get(`/api/repo/${e.repoId}/files/counts`).then(function(ue){h||U!==e.repoId||(e.fileCounts=ue.data)},function(){e.fileCounts={}})}function R(){return e.files.filter(U=>U.name==e.paths[e.paths.length-1]&&U.path==e.paths.slice(0,e.paths.length-1).join("/"))[0]}var I=null;e.on("dispose",function(){I&&a.interval.cancel(I)});function C(U){if(h)return;let ue=e.repoId;t.get(`/api/repo/${e.repoId}/options`).then(te=>{if(!(h||ue!==e.repoId)){if(e.options=te.data,e.options.url){window.location=e.options.url;return}U&&U(te.data)}},te=>{if(!(h||ue!==e.repoId)){var re=te.data||{};if(re.error==="rate_limited"&&re.resetAt){let me=function(){var be=Math.max(0,Math.ceil((e.rateLimitResetAt-Date.now())/1e3));if(be<=0)e.rateLimitCountdown="",e.rateLimitResetAt=0,I&&(a.interval.cancel(I),I=null),C(U);else{var he=Math.floor(be/60),le=be%60;e.rateLimitCountdown=he>0?he+"m "+le+"s":le+"s"}};var pe=me;e.type="rate_limited",e.rateLimitResetAt=re.resetAt,I&&a.interval.cancel(I),me(),I=a.interval(me,1e3)}else re.error==="rep Line truncated
`+JSON.stringify(t):""}`},[Be.NAVIGATION_GUARD_REDIRECT]({from:e,to:t}){return`Redirected from "${e.fullPath}" to "${vc(t)}" via a navigation guard.`},[Be.NAVIGATION_ABORTED]({from:e,to:t}){return`Navigation aborted from "${e.fullPath}" to "${t.fullPath}" via a navigation guard.`},[Be.NAVIGATION_CANCELLED]({from:e,to:t}){return`Navigation cancelled from "${e.fullPath}" to "${t.fullPath}" with a new navigation.`},[Be.NAVIGATION_DUPLICATED]({from:e,to:t}){return`Avoided redundant navigation to current location: "${e.fullPath}".`}};function os(e,t){return qe(new Error,{type:e,[na]:!0},t)}function Vt(e,t){return e instanceof Error&&na in e&&(t==null||!!(e.type&t))}var hc=["params","query","hash"];function vc(e){if(typeof e=="string")return e;if(e.path!=null)return e.path;let t={};for(let o of hc)o in e&&(t[o]=e[o]);return JSON.stringify(t,null,2)}function oa(e){let t={};if(e===""||e==="?")return t;let o=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;r<o.length;++r){let n=o[r].replace(Gi," "),i=n.indexOf("="),a=_s(i<0?n:n.slice(0,i)),u=i<0?null:_s(n.slice(i+1));if(a in t){let f=t[a];gt(f)||(f=t[a]=[f]),f.push(u)}else t[a]=u}return t}function Go(e){let t="";for(let o in e){let r=e[o];if(o=ic(o),r==null){r!==void 0&&(t+=(t.length?"&":"")+o);continue}(gt(r)?r.map(n=>n&&Mo(n)):[r&&Mo(r)]).forEach(n=>{n!==void 0&&(t+=(t.length?"&":"")+o,n!=null&&(t+="="+n))})}return t}function ra(e){let t={};for(let o in e){let r=e[o];r!==void 0&&(t[o]=gt(r)?r.map(n=>n==null?null:""+n):r==null?r:""+r)}return t}var ia=Symbol(""),jo=Symbol(""),Gn=Symbol(""),jn=Symbol(""),Wn=Symbol("");function Cs(){let e=[];function t(r){return e.push(r),()=>{let n=e.indexOf(r);n>-1&&e.splice(n,1)}}function o(){e=[]}return{add:t,list:()=>e.slice(),reset:o}}function Bt(e,t,o,r,n,i=a=>a()){let a=r&&(r.enterCallbacks[n]=r.enterCallbacks[n]||[]);return()=>new Promise((u,f)=>{let h=p=>{p===!1?f(os(Be.NAVIGATION_ABORTED,{from:o,to:t})):p instanceof Error?f(p):mc(p)?f(os(Be.NAVIGATION_GUARD_REDIRECT,{from:t,to:p})):(a&&r.enterCallbacks[n]===a&&typeof p=="function"&&a.push(p),u())},v=i(()=>e.call(r&&r.instances[n],t,o,h)),k=Promise.resolve(v);e.length<3&&(k=k.then(h)),k.catch(p=>f(p))})}function Kn(e,t,o,r,n=i=>i()){let i=[];for(let a of e)for(let u in a.components){let f=a.components[u];if(!(t!=="beforeRouteEnter"&&!a.instances[u]))if(Hi(f)){let h=(f.__vccOpts||f)[t];h&&i.push(Bt(h,o,r,a,u,n))}else{let h=f();i.push(()=>h.then(v=>{if(!v)throw new Error(`Couldn't resolve component "${u}" at "${a.path}"`);let k=Qu(v)?v.default:v;a.mods[u]=v,a.components[u]=k;let p=(k.__vccOpts||k)[t];return p&&Bt(p,o,r,a,u,n)()}))}}return i}function aa(e,t){let o=[],r=[],n=[],i=Math.max(t.matched.length,e.matched.length);for(let a=0;a<i;a++){let u=t.matched[a];u&&(e.matched.find(h=>ns(h,u))?r.push(u):o.push(u));let f=e.matched[a];f&&(t.matched.find(h=>ns(h,f))||n.push(f))}return[o,r,n]}var yc=()=>location.protocol+"//"+location.host;function ga(e,t){let{pathname:o,search:r,hash:n}=t,i=e.indexOf("#");if(i>-1){let a=n.includes(e.slice(i))?e.slice(i).length:1,u=n.slice(a);return u[0]!=="/"&&(u="/"+u),Fo(u,"")}return Fo(o,e)+r+n}function gc(e,t,o,r){let n=[],i=[],a=null,u=({state:p})=>{let g=ga(e,location),w=o.value,b=t.value,T=0;if(p){if(o.value=g,t.value=p,a&&a===w){a=null;return}T=b?p.position-b.position:0}else r(g);n.forEach(N=>{N(o.value,w,{delta:T,type:Hn.pop,direction:T?T>0?Bn.forward:Bn.back:Bn.unknown})})};function f(){a=o.value}function h(p){n.push(p);let g=()=>{let w=n.indexOf(p);w>-1&&n.splice(w,1)};return i.push(g),g}function v(){if(document.visibilityState==="hidden"){let{history:p}=window;if(!p.state)return;p.replaceState(qe({},p.state,{scroll:on()}),"")}}function k(){for(let p of i)p();i=[],window.removeEventListener("popstate",u),window.removeEventListener("pagehide",v),document.removeEventListener("visibilitychange",v)}return window.addEventListener("popstate",u),window.addEventListener("pagehide",v),document.addEventListener("visibilitychange",v),{pauseListeners:f,listen:h,destroy:k}}function la(e,t,o,r=!1,n=!1){return{back:e,current:t,forward:o,replaced:r,position:window.history.length,scroll:n?on():null}}function bc(e){let{history:t,location:o}=window,r={value:ga(e,o)},n={value:t.state};n.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(f,h,v){let k=e.indexOf("#"),p=k>-1?(o.host&&document.querySelector("base")?e:e.slice(k))+f:yc()+e+f;try{t[v?"replaceState":"pushState"](h,"",p),n.value=h}catch(g){console.error(g),o[v?"replace":"assign"](p)}}function a(f,h){i(f,qe({},t.state,la(n.value.back,f,n.value.forward,!0),h,{position:n.value.position}),!0),r.value=f}function u(f,h){let v=qe({},n.value,t.state,{forward:f,scroll:on()});i(v.current,v,!0),i(f,qe({},la(r.value,f,null),{position:v.position+1},h),!1),r.value=f}return{location:r,state:n,push:u,replace:a}}function ba(e){e=Zi(e);let t=bc(e),o=gc(e,t.state,t.location,t.replace);function r(i,a=!0){a||o.pauseListeners(),history.go(i)}let n=qe({locatLine truncated
`)),e.options=Object.assign({},e.options,f.options)})}i();let a=null;e.saveDefault=u=>{u&&u.preventDefault&&u.preventDefault();let f={terms:e.terms.split(`
`).map(h=>h.trim()).filter(h=>h.length>0),options:e.options};e.saving=!0,e.error=null,t.post("/api/user/default",f).then(()=>{i(),e.saving=!1,e.message="Saved",a&&r.cancel(a),a=r(()=>{e.message=null},2500)},h=>{e.saving=!1;let v=h&&h.data&&h.data.error;o("ERRORS."+v).then(k=>{e.error=k},()=>{e.error="Unable to save your defaults. Please try again."})})},e.deleteAccount=()=>{confirm("Delete your account? All your anonymized repositories, gists, and pull requests will be removed, and your personal data will be erased. This cannot be undone.")&&(e.deletingAccount=!0,t.delete("/api/user").then(()=>{window.location.href="/"},()=>{e.deletingAccount=!1,e.deleteError="Unable to delete the account. Please try again."}))}},Ra=function(e,t,o){e.repoId=null,e.repoUrl=null,e.claim=()=>{t.post("/api/repo/claim",{repoId:e.repoId,repoUrl:e.repoUrl}).then(r=>{o.url("/dashboard")},r=>{e.error=r.data,e.claimForm.repoUrl.setValidity("not_found",!1),e.claimForm.repoId.setValidity("not_found",!1)})}},Ta=function(e,t,o,r,n){e.user&&!e.user.status&&o.url("/dashboard"),e.watch("user.status",()=>{e.user&&!e.user.status&&o.url("/dashboard")}),e.features=[{key:"anonymize",num:"01",eyebrow:"Anonymize",title:"Double-anonymous,",accent:"your rules.",text:"Choose what reviewers may see: links, images, PDFs, notebooks, GitHub Pages. Add your own terms, with regex if you need it, and pick the expiration date.",cta:"Start an anonymization",href:"/anonymize",url:"anonymous.4open.science/anonymize",img:"/imgs/anonymize.png",alt:"The anonymize form: source repository, terms to redact, options and a live README preview"},{key:"review",num:"02",eyebrow:"Review",title:"Reviewers browse",accent:"the real thing.",text:"Highlighted source code, rendered PDFs, images, and notebooks, in a familiar file explorer. GitHub Pages is also supported.",cta:"Open the example",href:"https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/",target:"_self",url:"anonymous.4open.science/r/840c8c57-\u2026",img:"/imgs/explorer.png",alt:"The repository explorer with a file tree and a rendered README"},{key:"manage",num:"03",eyebrow:"Manage",title:"One dashboard,",accent:"until the decision.",text:"Monitor views, edit configuration, remove or update your repository. Program chairs can group submissions under a conference with one shared expiry.",cta:"Open the dashboard",href:"/dashboard",needsUser:!0,url:"anonymous.4open.science/dashboard",img:"/imgs/dashboard.png",alt:"The dashboard listing anonymized repositories with status, views and expiry"}],e.feature=e.features[0].key,e.selectFeature=function(u){e.feature=u},e.featureHref=function(u){return u.needsUser&&!e.user?"/signin":u.href},e.featureTarget=function(u){return u.needsUser&&!e.user?"_self":u.target||void 0},e.featureKeydown=function(u,f){let h={ArrowDown:1,ArrowRight:1,ArrowUp:-1,ArrowLeft:-1,Home:"first",End:"last"}[u.key];if(h===void 0)return;u.preventDefault();let v=e.features.length,k=h==="first"?0:h==="last"?v-1:(f+h+v)%v;e.feature=e.features[k].key,n(()=>{let p=r.document.getElementById("feature-tab-"+e.feature);p&&p.focus()})},e.cards=[{key:"repositories",total:0,label:"repositories anonymized"},{key:"users",total:0,label:"researchers"},{key:"pageViews",total:0,label:"page views"},{key:"pullRequests",total:0,label:"pull requests"}];function i(){t.get("/api/stat/").then(u=>{e.stat=u.data,e.cards[0].total=u.data.nbRepositories,e.cards[1].total=u.data.nbUsers,e.cards[2].total=u.data.nbPageViews,e.cards[3].total=u.data.nbPullRequests})}i();function a(u){let f={series:u,bars:[],viewW:100,deltaToday:0,pctChange:0,pctAbs:0,isUp:!0};if(!u||u.length<2)return f;let h=new Array(u.length-1);for(let b=1;b<u.length;b++)h[b-1]=u[b]-u[b-1];let v=h.length,k=Math.max.apply(null,h),p=Math.min.apply(null,h),g=Math.min(0,p),w=k-g||1;f.viewW=v*2,f.bars=new Array(v);for(let b=0;b<v;b++){let T=(h[b]-g)/w,N=Math.max(1.5,T*34);f.bars[b]={x:(b*2+.25).toFixed(2),y:(36-N).toFixed(2),w:"1.5",h:N.toFixed(2)}}if(f.deltaToday=h[v-1],v>=2){let b=h[v-2];b&&(f.pctChange=(f.deltaToday-b)/b*100)}return f.pctAbs=Math.round(Math.abs(f.pctChange)),f.isUp=f.pctChange>=0,f}e.history={repositories:a([]),users:a([]),pageViews:a([]),pullRequests:a([])},t.get("/api/stat/history?days=60").then(u=>{let f=u.data||[];e.history={repositories:a(f.map(h=>h.nbRepositories||0)),users:a(f.map(h=>h.nbUsers||0)),pageViews:a(f.map(h=>h.nbPageViews||0)),pullRequests:a(f.map(h=>h.nbPullRequests||0))}})},Oa=function(e,t,o,r,n,i){let a=mt();e.on("routeLeave",function(){$('[data-toggle="tooltip"]').tooltip("dispose")}),e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),a.timeout(()=>{$('[data-toggle="tooltip"]').tooltip()},250),e.items=[],e.search="",e.loading=!0,e.statusKeyLabels={ready:"Ready",progress:"In progress",error:"Error",expired:"Expired",removed:"Removed"};let u=["queue","download","downloaded","preparing","anonymizing"];function f(C){return C==="ready"||C==="error"?C:C==="expired"||C==="expiring"?"expired":C===" Line truncated
`)),e.options=Object.assign({},e.options,j.options),e.options.expirationDate=j.options&&j.options.expirationDate?new Date(j.options.expirationDate):f(),D&&D()})}function p(D,V,j){e.anonymize&&e.anonymize[D]&&e.anonymize[D].setValidity(V,j)}function g(D){try{let V=parseGithubUrl(D);if(V&&V.owner&&V.repo)return V.owner+"/"+V.repo}catch{}return null}function w(){return!e.isUpdate||!e._originalRepositoryID?void 0:g(e.sourceUrl)===e._originalFullName?e._originalRepositoryID:void 0}async function b(){e._preservingDraft=!0;try{await e.urlSelected(!0)}finally{e._preservingDraft=!1}}async function T(){let D;try{D=JSON.parse(sessionStorage.getItem("github-access-draft")||"null")}catch{return!1}if(!D||D.path!==n.path()||Date.now()-D.savedAt>=30*6e4)return!1;for(let V of["sourceUrl","terms","repoId","pullRequestId","gistId","source","options","conference","githubConnection"])Object.prototype.hasOwnProperty.call(D.draft,V)&&(e[V]=D.draft[V]);return e.options.expirationDate&&(e.options.expirationDate=new Date(e.options.expirationDate)),await b(),sessionStorage.removeItem("github-access-draft"),!0}k(()=>{!r.repoId&&!r.pullRequestId&&!r.gistId&&a(T,0),r.repoId&&r.repoId!=""&&(e.isUpdate=!0,e.detectedType="repo",e.repoId=r.repoId,t.get("/api/repo/"+e.repoId).then(async D=>{e.githubConnection=D.data.connection||"oauth",e.sourceUrl="https://github.com/"+D.data.source.fullName,e._originalFullName=D.data.source.fullName,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.role=D.data.role||"owner",e.coauthors=D.data.coauthors||[],e._originalBranch=D.data.source.branch,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,e.repositoryID=D.data.source.repositoryID,e._originalRepositoryID=D.data.source.repositoryID,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await T()&&(await Promise.all([N(),R()]),P())},()=>{n.url("/404")})),r.pullRequestId&&r.pullRequestId!=""&&(e.isUpdate=!0,e.detectedType="pr",e.pullRequestId=r.pullRequestId,t.get("/api/pr/"+e.pullRequestId).then(async D=>{if(e.githubConnection=D.data.connection||"oauth",e.sourceUrl="https://github.com/"+D.data.source.repositoryFullName+"/pull/"+D.data.source.pullRequestId,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await T())try{e.details=(await t.get(`/api/pr/${D.data.source.repositoryFullName}/${D.data.source.pullRequestId}`,{params:{connection:e.githubConnection}})).data}catch(V){let j=V&&V.data&&V.data.error;j&&(i("ERRORS."+j).then(oe=>{e.addToast({title:"Error",date:new Date,body:oe}),e.error=oe},console.error),Se(j))}},()=>{n.url("/404")})),r.gistId&&r.gistId!=""&&(e.isUpdate=!0,e.detectedType="gist",e.gistId=r.gistId,t.get("/api/gist/"+e.gistId).then(async D=>{e.sourceUrl="https://gist.github.com/"+D.data.source.gistId,e.terms=D.data.options.terms.filter(V=>V).join(`
`),e.source=D.data.source,e.options=Object.assign({},e.options,D.data.options),e.conference=D.data.conference,e._originalConference=D.data.conference,D.data.options.expirationDate&&(e.options.expirationDate=new Date(D.data.options.expirationDate)),!await T()&&await se()},()=>{n.url("/404")}))}),t.get("/github/connections").then(D=>{e.githubConnections=D.data,!r.repoId&&!r.pullRequestId&&!r.gistId&&e.githubConnection===void 0&&D.data.appEnabled&&D.data.appConnected&&(e.githubConnection="github-app")}).catch(()=>{}),e.urlSelected=async(D=!1)=>{D||(e.terms=e.defaultTerms),!D&&!e.isUpdate&&(e.repoId="",e.pullRequestId="",e.gistId=""),e.details=null,e.branches=[],D||(e.source={type:"GitHubStream",branch:"",commit:""}),e.anonymize_readme="",e.readme="",e.html_readme="",e.detectedType=null,e.gistOAuthRequired=!1;let V;try{V=parseGithubUrl(e.sourceUrl)}catch{p("sourceUrl","github",!1);return}p("sourceUrl","github",!0);try{V.gistId&&!V.repo?(e.detectedType="gist",e.source={gistId:V.gistId},await se()):V.pullRequestId?(e.detectedType="pr",e.source={repositoryFullName:V.owner+"/"+V.repo,pullRequestId:V.pullRequestId},await x()):(e.detectedType="repo",await Promise.all([N(),R()]),P())}catch{return}$('[data-toggle="tooltip"]').tooltip()},$('[data-toggle="tooltip"]').tooltip(),e.watch("source.branch",async()=>{if(e.detectedType!=="repo")return;let D=e.branches.filter(j=>j.name==e.source.branch)[0];if(!D)return;!(e.isUpdate&&e._originalBranch===e.source.branch&&!!e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=D.commit),e.readme=D.readme,await R(),P()}),e.getBranches=async D=>{let V=parseGithubUrl(e.sourceUrl);try{let j=await t.get(`/api/repo/${V.owner}/${V.repo}/branches`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&g(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:D===!0?"1":"0",repositoryID:w()}});e.branches=j.data,e.sourceUnreachable=!1,e.source.branch||(e.source.branch=e.details.defaultBranch);let oe=e.branches.filter(le=>le.name==e.source.branch);oe.length>0&&(!(!D&&e.isUpdate&&!e.options.update&&e._originalBranch===e.source.branch&&e.source.commit)&&!(e._preservingDraft&&e.source.commit)&&(e.source.commit=oe[0].commit),e.readme=oe[0].readme,await R(D))}catch(j){e.branches=[],e.sourceUnreachable=j&&(j.status===404||j.data&&j.data.error==="repo_not_found");let oe=j&&j.data&&j.data.error||(j&&j.status===404?"repo_not_found":"unknown_error");i("ERRORS."+oe).then(le=>{e.toasts=e.toasts||[],e.addToast({title:"Error",date:new Date,body:le}),e.error=le},console.error),typeof p=="function"&&p("sourceUrl","missing",!1)}};async function N(){let D=parseGithubUrl(e.sourceUrl);try{Te();let V=await t.get(`/api/repo/${D.owner}/${D.repo}/`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&g(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,repositoryID:w(),force:"1"}});e.details=V.data,e.details&&e.details.id&&(e.repositoryID=e.details.id),e.repoId||(e.repoId=e.details.repo+"-"+generateRandomId(4)),await e.getBranches()}catch(V){throw V.data&&(i("ERRORS."+V.data.error).then(j=>{e.addToast({title:"Error",date:new Date,body:j}),e.error=j},console.error),Se(V.data.error)),p("sourceUrl","missing",!1),V}}async function R(D){if(e.readme&&!D)return e.readme;let V=parseGithubUrl(e.sourceUrl);try{let j=await t.get(`/api/repo/${V.owner}/${V.repo}/readme`,{params:{anonymizedRepoId:e.isUpdate&&r.repoId&&g(e.sourceUrl)===e._originalFullName?r.repoId:void 0,connection:e.githubConnection,force:D===!0?"1":"0",branch:e.source.branch,repositoryID:w()}});e.readme=j.data}catch{e.readme=""}}function I(){let D={terms:e.terms?e.terms.split(`
`):[],image:!!e.options.image,link:!!e.options.link,repoId:e.repoId};e.source&&e.source.branch&&(D.branchName=e.source.branch);try{let V=parseGithubUrl(e.sourceUrl);D.repoName=`${V.owner}/${V.repo}`}catch{}return D}function C(D,V){let j=null,oe=0;return function(){j&&a.cancel(j),j=a(()=>{j=null;let L=++oe,te=D();te&&t.post("/api/anonymize-preview",te).then(U=>{L===oe&&V(U.data)},()=>{})},200)}}let A=C(()=>e.readme?{content:e.readme,options:I()}:null,D=>{e.anonymize_readme=D.content||"";let V="";try{let oe=parseGithubUrl(e.sourceUrl),le=e.source.branch||e.details&&e.details.defaultBranch||"main";V=`https://github.com/${oe.owner}/${oe.repo}/raw/${le}/`}catch{}let j=renderMD(e.anonymize_readme,V);e.html_readme=j,a(Prism.highlightAll,150)});function P(){!e.anonymize||!e.anonymize.terms||(e.termsRegexWarning=!!e.terms&&!!e.terms.match(/[-[\]{}()*+?.,\\^$|#]/g),A())}async function x(){let D=parseGithubUrl(e.sourceUrl);try{Te();let V=await t.get(`/api/pr/${D.owner}/${D.repo}/${D.pullRequestId}`,{params:{connection:e.githubConnection}});e.details=V.data,e.pullRequestId||(e.pullRequestId=D.repo+"-PR"+D.pullRequestId+"-"+generateRandomId(4))}catch(V){throw V.data&&(i("ERRORS."+V.data.error).then(j=>{e.addToast({title:"Error",date:new Date,body:j}),e.error=j},console.error),Se(V.data.error)),p("sourceUrl","missing",!1),V}}let Q=ze(new Map),K=new Set;function z(){let D=new Set,V=e.details&&e.details.pullRequest;if(!V)return D;typeof V.title=="string"&&D.add(V.title),typeof V.body=="string"&&D.add(V.body),typeof V.diff=="string"&&D.add(V.diff);let j=V.comments||[];for(let oe of j)typeof oe.author=="string"&&D.add(oe.author),typeof oe.body=="string"&&D.add(oe.body);return D}let ae=C(()=>{let D=z();K=D;let V=Array.from(D);return V.length===0?null:{contents:V,options:I()}},D=>{if(!D||!Array.isArray(D.contents))return;let V=Array.from(K),j=new Map;for(let oe=0;oe<V.length&&oe<D.contents.length;oe++)j.set(V[oe],D.contents[oe]);Q.clear(),j.forEach((oe,le)=>Q.set(le,oe))});e.anonymizePrContent=function(D){return D&&(Q.has(D)?Q.get(D):(K.has(D)||ae(),D))};async function se(){let D=parseGithubUrl(e.sourceUrl);try{if(Te(),e.gistOAuthRequired=!1,e.githubConnections?.oauthConnected===!1){e.gistOAuthRequired=!0,p("sourceUrl","missing",!1);return}let V=await t.get(`/api/gist/source/${D.gistId}`);e.details=V.data,e.gistId||(e.gistId="gist-"+D.gistId.substring(0,6)+"-"+generateRandomId(4))}catch(V){if(V.data?.error==="github_oauth_required"){e.gistOAuthRequired=!0,p("sourceUrl","missing",!1);return}throw V.data&&(i("ERRORS."+V.data.error).then(j=>{e.addToast({title:"Error",date:new Date,body:j}),e.error=j},console.error),Se(V.data.error)),p("sourceUrl","missing",!1),V}}let re=ze(new Map),ue=new Set;function ke(){let D=new Set,V=e.details&&e.details.gist;if(!V)return D;typeof V.description=="string"&&D.add(V.description),typeof V.ownerLogin=="string"&&D.add(V.ownerLogin);let j=V.files||[];for(let le of j)typeof le.filename=="string"&&D.add(le.filename),typeof le.content=="string"&&D.add(le.content);let oe=V.comments||[];for(let le of oe)typeof le.author=="string"&&D.add(le.author),typeof le.body=="string"&&D.add(le.body);return D}let he=C(()=>{let D=ke();ue=D;let V=Array.from(D);return V.length===0?null:{contents:V,options:I()}},D=>{if(!D||!Array.isArray(D.contents))return;let V=Array.from(ue),j=new Map;for(let oe=0;oe<V.length&&oe<D.contents.length;oe++)j.set(V[oe],D.contents[oe]);re.clear(),j.forEach((oe,le)=>re.set(le,oe)),me()});e.anonymizeGistContent=function(D){return D&&(re.has(D)?re.get(D):(ue.has(D)||he(),D))},e.previewGistFiles=[];function me(){let D=e.details&&e.details.gist&&e.details.gist.files||[];e.previewGistFiles=D.map(V=>({filename:e.anonymizeGistContent(V.filename),content:e.anonymizeGistContent(V.content),language:V.language}))}e.watch("details.gist.files",me,!0),e.watch("terms",me);function pe(){let D=e.conference;if(e.conference_data=null,!D)return;let V=e.isUpdate&&D===e._originalConference;t.get("/api/conferences/"+D).then(j=>{e.conference===D&&(e.conference_data=j.data,e.conference_data.startDate=new Date(e.conference_data.startDate),e.conference_data.endDate=new Date(e.conference_data.endDate),!V&&(e.options.expirationDate=new Date(e.conference_data.endDate),e.options.expirationMode="remove",e.options.update=e.conference_data.options.update,e.options.image=e.conference_data.options.image,e.options.pdf=e.conference_data.options.pdf,e.options.notebook=e.conference_data.options.notebook,e.options.link=e.conference_data.options.link))},()=>{e.conference===D&&(e.conference_data=null)})}function Te(){p("repoId","used",!0),p("repoId","format",!0),p("pullRequestId","used",!0),p("pullRequestId","format",!0),p("gistId","used",!0),p("gistId","format",!0),p("sourceUrl","used",!0),p("sourceUrl","missing",!0),p("sourceUrl","access",!0),p("sourceUrl","github",!0),p("commit","exists",!0),p("conference","activated",!0),p("terms","format",!0),e.termsRegexWarning=!1}function Re(){let D=e.anonymize&&e.anonymize.expirationDate;ret Line truncated
`).filter(le=>le),connection:e.githubConnection,fullName:`${V.owner}/${V.repo}`,repository:e.sourceUrl,options:e.options,source:e.source,conference:e.conference};e.details&&(j.options.pageSource=e.details.pageSource),Te();let oe=e.isUpdate?"/api/repo/"+e.repoId:"/api/repo/";t.post(oe,j,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/status/"+e.repoId},le=>{le.data&&(i("ERRORS."+le.data.error).then(L=>{e.error=L},console.error),Se(le.data.error))}).finally(()=>{D.target.disabled=!1})},e.anonymizeGist=D=>{if(Re())return;D.target.disabled=!0;let V=parseGithubUrl(e.sourceUrl),j={gistId:e.gistId,terms:e.terms.trim().split(`
`).filter(le=>le),source:{gistId:V.gistId},options:e.options,conference:e.conference};Te();let oe=e.isUpdate?"/api/gist/"+e.gistId:"/api/gist/";t.post(oe,j,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/gist/"+e.gistId},le=>{le.data&&(i("ERRORS."+le.data.error).then(L=>{e.error=L},console.error),Se(le.data.error))}).finally(()=>{D.target.disabled=!1})},e.anonymizePullRequest=D=>{if(Re())return;D.target.disabled=!0;let V=parseGithubUrl(e.sourceUrl),j={pullRequestId:e.pullRequestId,connection:e.githubConnection,terms:e.terms.trim().split(`
`).filter(le=>le),source:{repositoryFullName:`${V.owner}/${V.repo}`,pullRequestId:V.pullRequestId},options:e.options,conference:e.conference};Te();let oe=e.isUpdate?"/api/pr/"+e.pullRequestId:"/api/pr/";t.post(oe,j,{headers:{"Content-Type":"application/json"}}).then(()=>{window.location.href="/pr/"+e.pullRequestId},le=>{le.data&&(i("ERRORS."+le.data.error).then(L=>{e.error=L},console.error),Se(le.data.error))}).finally(()=>{D.target.disabled=!1})},e.watch("conference",()=>{pe()}),e.watch("terms",()=>{e.detectedType==="repo"&&P(),e.detectedType==="pr"&&ae(),e.detectedType==="gist"&&he()}),e.watch("options.image",()=>{e.detectedType==="repo"&&P(),e.detectedType==="pr"&&ae(),e.detectedType==="gist"&&he()}),e.watch("options.link",()=>{e.detectedType==="repo"&&P(),e.detectedType==="pr"&&ae(),e.detectedType==="gist"&&he()}),e.watch("details",()=>{e.detectedType==="pr"&&ae(),e.detectedType==="gist"&&he()},!0)},Yo=function(e,t,o,r,n,i){let a=mt(),u=as(),f=0,h=!1;e.on("dispose",()=>{h=!0,f++,v&&v.resolve()}),e.files=[],e.isMac=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),e.fileSearchQuery="",e.fileSearchResults=null,e.fileSearchLoading=!1,u(document,"keydown",function(z){if((z.metaKey||z.ctrlKey)&&z.key==="k"){z.preventDefault();var ae=document.querySelector(".tree-search-input");ae&&(ae.focus(),ae.select())}});var v=null;e.onFileSearchChange=function(){v&&(v.resolve(),v=null);let z=e.fileSearchQuery;if(!z||z.length<2){e.fileSearchResults=null,e.fileSearchLoading=!1;return}e.fileSearchLoading=!0;let ae=i.defer();v=ae,t.get(`/api/repo/${e.repoId}/files/search?q=${encodeURIComponent(z)}`,{timeout:ae.promise}).then(function(se){if(!(h||v!==ae)){v=null,e.fileSearchLoading=!1;var re={};e.files.forEach(function(L){re[(L.path||"")+"/"+L.name]=!0});for(var ue=[],ke={},he=0;he<se.data.length;he++){var me=se.data[he];if(me.path)for(var pe=me.path.split("/"),Te="",Re=0;Re<pe.length;Re++){var Se=Te;Te=Te?Te+"/"+pe[Re]:pe[Re];var D=Se+"/"+pe[Re];!re[D]&&!ke[D]&&(ke[D]=!0,ue.push({name:pe[Re],path:Se}))}}ue.sort(function(L,te){return(L.path||"").split("/").length-(te.path||"").split("/").length}),ue.length>0&&e.files.push.apply(e.files,ue);for(var V=[],j=0;j<se.data.length;j++){var oe=se.data[j],le=(oe.path||"")+"/"+oe.name;!re[le]&&oe.size!=null&&(V.push(oe),re[le]=!0)}V.length>0&&e.files.push.apply(e.files,V),e.fileSearchResults=se.data}},function(){!h&&v===ae&&(v=null,e.fileSearchLoading=!1,e.fileSearchResults=[])})};let k={yml:"yaml",txt:"text",py:"python",js:"javascript",ts:"typescript"},p=["license","txt"],g=["png","jpg","jpeg","gif","svg","ico","bmp","tiff","tif","webp","avif","heif","heic"],w=["wav","mp3","ogg","wma","flac","aac","m4a"],b=["mp4","avi","webm","mov","mpg","mpeg","mkv","flv","wmv","3gp","3g2","m4v","f4v","f4p","f4a","f4b"];e.on("routeUpdate",function(z,ae){if(e.repoId!=r.repoId)return K();if((r.path||"")!=e.filePath){e.filePath=r.path||"",e.paths=e.filePath.split("/").filter(se=>se&&se.trim().length>0),Q();for(let se=0;se<e.paths.length-1;se++){let re=se>0?e.paths.slice(0,se).join("/"):"";e.files.some(ke=>ke.path===re)||e.getFiles(re)}}});function T(){if(e.paths[0]!="")return;let z=["readme.md","readme.txt","readme.org","readme.1st","readme"],ae={};for(let re of e.files)re.name.toLowerCase().indexOf("readme")>-1&&(ae[re.name.toLowerCase()]=re.name);let se=null;for(let re of z)if(ae[re]){se=re;break}if(!se&&Object.keys(ae).length>0&&(se=Object.keys(ae)[0]),se){let re=o.url();re[re.length-1]!="/"&&(re+="/"),o.url(re+encodePathForUrl(ae[se]))}}e.fileCounts=null,e.getFiles=function(z){let ae=e.repoId;return t.get(`/api/repo/${e.repoId}/files/?path=${encodeURIComponent(z)}&v=${e.options.lastUpdateDate}`).then(function(se){if(h||ae!==e.repoId)return[];let re=z||"";return e.files=e.files.filter(ue=>ue.path!==re),e.files.push(...se.data),se.data},function(se){if(h||ae!==e.repoId)return[];e.type="error",e.content=se&&se.data&&se.data.error||"unknown_error",e.files=[]})};function N(){let z=e.repoId;t.get(`/api/repo/${e.repoId}/files/counts`).then(function(ae){h||z!==e.repoId||(e.fileCounts=ae.data)},function(){e.fileCounts={}})}function R(){return e.files.filter(z=>z.name==e.paths[e.paths.length-1]&&z.path==e.paths.slice(0,e.paths.length-1).join("/"))[0]}var I=null;e.on("dispose",function(){I&&a.interval.cancel(I)});function C(z){if(h)return;let ae=e.repoId;t.get(`/api/repo/${e.repoId}/options`).then(se=>{if(!(h||ae!==e.repoId)){if(e.options=se.data,e.options.url){window.location=e.options.url;return}z&&z(se.data)}},se=>{if(!(h||ae!==e.repoId)){var re=se.data||{};if(re.error==="rate_limited"&&re.resetAt){let ke=function(){var he=Math.max(0,Math.ceil((e.rateLimitResetAt-Date.now())/1e3));if(he<=0)e.rateLimitCountdown="",e.rateLimitResetAt=0,I&&(a.interval.cancel(I),I=null),C(z);else{var me=Math.floor(he/60),pe=he%60;e.rateLimitCountdown=me>0?me+"m "+pe+"s":pe+"s"}};var ue=ke;e.type="rate_limited",e.rateLimitResetAt=re.resetAt,I&&a.interval.cancel(I),ke(),I=a.interval(ke,1e3)}else re.error==="re Line truncated
`+b.join(`
`)],{type:"text/csv"}),N=document.createElement("a");N.href=URL.createObjectURL(T),N.download=r.username+"-repositories.csv",N.click()},e.showStatusMessage=m=>{let w=m.statusMessage||"(no message)";window.prompt(`Status message for ${m.repoId} (${m.status}):`,w)},e.fetchGithubInfo=m=>{let w=window.open("","_blank");w&&w.document.write("<pre>Loading GitHub info for "+m.repoId+"...</pre>"),t.get("/api/admin/repos/"+m.repoId+"/github").then(k=>{w&&(w.document.open(),w.document.write('<pre style="font:13px monospace;padding:16px;white-space:pre-wrap">'+JSON.stringify(k.data,null,2).replace(/[<>]/g,b=>b==="<"?"&lt;":"&gt;")+"</pre>"),w.document.close())},k=>{let b=k&&k.data?JSON.stringify(k.data,null,2):String(k);w&&(w.document.body.innerHTML='<pre style="color:#B42318;padding:16px">'+b+"</pre>")})};function p(m){t.get("/api/admin/users/"+m+"/repos",{}).then(w=>{e.repositories=w.data},w=>{console.error(w)})}function h(m){t.get("/api/admin/users/"+m,{}).then(w=>{e.userInfo=w.data},w=>{console.error(w)})}h(r.username),p(r.username),e.banUser=()=>{confirm(`Ban user ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/ban`).then(()=>h(r.username),m=>console.error(m))},e.activateUser=()=>{t.post(`/api/admin/users/${r.username}/activate`).then(()=>h(r.username),m=>console.error(m))},e.promoteUser=()=>{confirm(`Promote ${r.username} to admin?`)&&t.post(`/api/admin/users/${r.username}/promote`).then(()=>h(r.username),m=>console.error(m))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/demote`).then(()=>h(r.username),m=>console.error(m))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function y(){t.get("/api/admin/tokens").then(m=>{e.tokens=m.data||[]},m=>{m.status!==401&&m.status!==403&&console.error(m)})}y(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(m=>{e.tokenForm.plaintext=m.data.token,e.tokenForm.name="",y()},m=>console.error(m))},e.revokeToken=m=>{confirm(`Revoke token "${m.name}"?`)&&t.delete("/api/admin/tokens/"+m.id).then(()=>y(),w=>console.error(w))},e.removeCache=m=>{confirm("Remove cached files for "+m.repoId+"?")&&t.delete("/api/admin/repos/"+m.repoId).then(()=>p(r.username),w=>console.error(w))},e.removeRepository=m=>{confirm("Remove repository "+m.repoId+"?")&&t.delete("/api/repo/"+m.repoId+"/").then(()=>p(r.username),w=>console.error(w))},e.updateRepository=m=>{let w=ze({title:`Refreshing ${m.repoId}...`,date:new Date,body:`The repository ${m.repoId} is going to be refreshed.`});e.toasts.push(w),t.post(`/api/repo/${m.repoId}/refresh`).then(k=>{k.data.status=="ready"?w.title=`${m.repoId} is refreshed.`:w.title=`Refreshing of ${m.repoId}.`},k=>{w.title=`Error during the refresh of ${m.repoId}.`,w.body=k.body})},e.getGitHubRepositories=m=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(w=>{e.userInfo.repositories=w.data})};let g=null;e.watch("query",()=>{n.timeout.cancel(g),g=n.timeout(()=>{p(r.username)},500)},!0)},Ua=function(e,t,o){let r=mt(),n=as();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let i=k=>{if(k.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){k.preventDefault();let b=document.querySelector('.admin-filter-toolbar input[type="search"]');b&&b.focus()}};n(document,"keydown",i),e.on("dispose",()=>document.removeEventListener("keydown",i)),e.clearFilter=k=>{k==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[k]="",e.query.page=1},e.chips=[];let a=()=>{let k=[];(e.query.dateFrom||e.query.dateTo)&&k.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=k};e.statusCountFor=k=>{let b=(e.statusCounts||[]).find(T=>T._id===k);return b?b.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=k=>{e.query.sort===k?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=k,e.query.direction="desc"),e.query.page=1},e.sortIcon=k=>e.query.sort===k?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let u="admin.conferences.filterPrefs",p={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},h=loadFilterPrefs(u)||{};e.query=Object.assign({},p,h,{page:1,search:""});let y=o.search();y.search&&(e.query.search=y.search);let g="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(g)||"[]"),e.savePreset=()=>{let k=window.prompt("Preset name:");if(!k)return;let b=Object.assign({},e.query);delete b.page,e.presets=(e.presets||[]).filter(T=>T.name!==k),e.presets.push({name:k,query:b}),localStorage.setItem(g,JSON.stringify(e.presets))},e.applyPreset=k=>{Object.assign(e.query,k.query,{page:1})},e.deletePreset=k=>{e.presets=(e.presets||[]).filter(b=>b.name!==k.name),localStorage.setItem(g,JS Line truncated
`).map((be,he)=>he===0?be:me+be).join(`
`)}else typeof K=="number"||typeof K=="boolean"?pe=String(K):pe=JSON.stringify(K);x.push(`${te}${pe}${re}`)}),x.push("}"),x.join(`
`)}function h(N){let R=[],I="",C=/(\w+):(>=|<=|!=|>|<|=)?([^\s]+)/g,A=0,q;for(;q=C.exec(N);)I+=N.slice(A,q.index),A=C.lastIndex,R.push({key:q[1],op:q[2]||"=",val:q[3]});return I+=N.slice(A),{filters:R,free:I.trim().toLowerCase()}}function y(N,R){for(let I of R.filters){let C=(q,x,X)=>{let K=parseFloat(q),U=parseFloat(x);return X==="="?String(q)===String(x):X==="!="?String(q)!==String(x):X===">="?K>=U:X==="<="?K<=U:X===">"?K>U:X==="<"?K<U:!0},A;if(I.key==="code")A=N.displayMessage;else if(I.key==="module")A=N.module;else if(I.key==="status")A=N._status;else if(I.key==="url")A=N._url;else if(I.key==="repo")A=N._repoId;else if(I.key==="level")A=N.level;else continue;if(A==null||!C(A,I.val,I.op))return!1}return!(R.free&&((N.displayMessage||"")+" "+(N.module||"")+" "+(N._url||"")+" "+JSON.stringify(N.raw||[])).toLowerCase().indexOf(R.free)===-1)}function g(){let N=h(e.query.search||"");e.parsedFilterCount=N.filters.length;let R=e.query.bucket,I=e.entries.filter(A=>R&&A._bucket!==R?!1:y(A,N)),C=e.query.group;if(C){let A=X=>C==="module"?X.module:X.displayMessage||X.message||"_",q=new Map;for(let X of I){let K=A(X);if(q.has(K)){let U=q.get(K);U.count++,U._related.push(X),new Date(X.ts)>new Date(U.ts)&&(U.ts=X.ts,U._url=X._url,U._status=X._status),new Date(X.ts)<new Date(U._firstSeen)&&(U._firstSeen=X.ts)}else{let U=Object.assign({},X);U._key=`${C}:${K}`,U._related=[X],U._firstSeen=X.ts,U._lastHourCount=0,U.count=1,q.set(K,U)}}let x=Date.now()-3600*1e3;for(let X of q.values())X._lastHourCount=X._related.filter(K=>new Date(K.ts).getTime()>=x).length;I=Array.from(q.values())}else I=I.map((A,q)=>(A._key="row:"+q+":"+A.ts,A._related=[A],A._firstSeen=A.ts,A._lastHourCount=0,A.count=1,A));e.query.sort==="count"?I.sort((A,q)=>q.count-A.count||new Date(q.ts)-new Date(A.ts)):I.sort((A,q)=>new Date(q.ts)-new Date(A.ts)),e.visible=I}function m(N){let R=N?e.entries.length:0,I=N?e.pageSize:Math.max(e.pageSize,e.entries.length||e.pageSize);t.get("/api/admin/errors",{params:{offset:R,limit:I}}).then(C=>{let A=(C.data.entries||[]).map(u);e.entries=N?e.entries.concat(A):A,e.available=!!C.data.available,e.cap=C.data.max||e.cap,e.total=C.data.total||e.entries.length,g()},C=>console.error(C))}e.loadMore=()=>m(!0),e.canLoadMore=()=>e.entries.length<e.total;function w(){t.get("/api/admin/errors/stats").then(N=>{let R=N.data||{},I=R.prev24h?Math.round((R.last24h-R.prev24h)/R.prev24h*100):0;e.stats={last24h:R.last24h||0,prev24h:R.prev24h||0,delta:I,severity:R.severity||{error:0,warn:0,info:0},unique:R.unique||{error:0,warn:0,info:0},buckets:R.buckets||[],dropped:R.dropped||0}},N=>console.error(N))}function k(){m(),w()}e.barPx=(N,R)=>{let I=e.stats.buckets||[],C=0;for(let X of I)C=Math.max(C,(X.error||0)+(X.warn||0)+(X.info||0));if(!C)return 0;let A=(N.error||0)+(N.warn||0)+(N.info||0);if(!A)return 0;let q=Math.round(A/C*60),x=N[R]||0;return Math.round(x/A*q)},e.bucketTitle=N=>`${new Date(N.hour).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})} \xB7 ${N.error||0} err \xB7 ${N.warn||0} warn \xB7 ${N.info||0} info`,e.toggle=N=>{e.expanded[N._key]=!e.expanded[N._key]},e.setBucket=N=>{e.query.bucket=N},e.refreshNow=k,e.clearAll=()=>{confirm("Clear all captured errors?")&&t.delete("/api/admin/errors").then(k,N=>console.error(N))},e.exportCsv=()=>{let N=["ts","level","module","displayMessage","_status","_url","_repoId"],R=[N.join(",")];for(let q of e.visible)R.push(N.map(x=>{let X=q[x]==null?"":String(q[x]);return/[",\n]/.test(X)?`"${X.replace(/"/g,'""')}"`:X}).join(","));let I=new Blob([R.join(`
`)],{type:"text/csv;charset=utf-8"}),C=URL.createObjectURL(I),A=document.createElement("a");A.href=C,A.download=`errors-${new Date().toISOString().slice(0,19)}.csv`,document.body.appendChild(A),A.click(),document.body.removeChild(A),URL.revokeObjectURL(C)};function b(N){e.copyHint=`${N} copied`,n.timeout(()=>{e.copyHint=""},1500)}e.copyJson=N=>{navigator.clipboard.writeText(N._detailJson).then(()=>b("JSON"))},e.copyCurl=N=>{if(!N._url)return;let I=`curl -X ${N._method||"GET"} '${window.location.origin}${N._url}'`;navigator.clipboard.writeText(I).then(()=>b("curl"))},k();let T=r(()=>{e.query.autoRefresh&&k()},15e3);e.on("dispose",()=>r.cancel(T)),e.watch("query.search",g),e.watch("query.bucket",g),e.watch("query.sort",g),e.watch("query.group",g)},Ba=function(e,t,o,r){if(e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null){o.url("/");return}e.data=null,e.loading=!0,e.error=null;function n(m){if(m==null)return"\u2014";for(var w=["B","KB","MB","GB","TB"],k=0,b=m;b>=1024&&k<w.length-1;)b/=1024,k++;return b.toFixed(k>0?1:0)+" "+w[k]}e.humanBytes=n;function i(m){if(!m)return"\u2014";var w=Math.floor(m/86400),k=Math.floor(m%86400/3600),b=Math.floor(m%3600/60);return w>0?w+"d "+(k<10?"0":"")+k+"h":k>0?k+"h "+(b<10?"0":"")+b+"m":b+"m"}e.humanDuration=i;function a(m){return m==null?"\u2014":m>=1e6?(m/1e6).toFixed(1)+"M":m>=1e3?(m/1e3).toFixed(1)+"K":String(m)}e.humanNum=a,e.queueTotal=function(m){return m?(m.waiting||0)+(m.active||0)+(m.delayed||0)+(m.failed||0):0},e.statusCount=function(m){if(!e.data||!e.data.repos)return 0;for(var w=e.data.repos.statusBreakdown||[],k=0;k<w.length;k++)if(w[k]._id===m)return w[k].count;return 0},e.barPct=function(m){var w=e.data&&e.data.repos?e.data.repos.total:0;if(!w)return 0;var k=[m];m==="expired"&&k.push("expiring"),m==="removed"&&k.push("removing"),m==="preparing"&&k.push("download");var b=0;return k.forEach(function(T){b+=e.statusCount(T)}),Math.max(.4,b/w*100)},e.errPct=function(m){if(!e.data||!e.data.errors)return 0;var w=Math.max(e.data.errors.severity.error,e.data.errors.severity.warn,e.data.errors.severity.info,1);return e.data.errors.severity[m]/w*100};function u(m){var w=m||[];return w.map(function(k,b){var T=w[b-1]||{},N=Object.assign({},k);return N.dailyRepositories=b?Math.max(0,(k.nbRepositories||0)-(T.nbRepositories||0)):0,N.dailyUsers=b?Math.max(0,(k.nbUsers||0)-(T.nbUsers||0)):0,N.dailyPageViews=b?Math.max(0,(k.nbPageViews||0)-(T.nbPageViews||0)):0,N})}function p(m){var w=m&&m.length?m[m.length-1]:{};return{repositories:w.dailyRepositories||0,users:w.dailyUsers||0,pageViews:w.dailyPageViews||0}}var h={};e.historyBarH=function(m,w){return!m||!h[w]?0:Math.max(1,Math.round(m[w]/h[w]*140))},e.historyLabel=function(m){if(!m||!m.date)return"";var w=new Date(m.date);return w.getUTCMonth()+1+"/"+w.getUTCDate()};function y(){t.get("/api/admin/overview").then(function(m){m.data.history=u(m.data.history),m.data.daily={today:p(m.data.history)},e.data=m.data,e.loading=!1,e.error=null,h={},(m.data.history||[]).forEach(function(w){["dailyPageViews","dailyRepositories","dailyUsers","nbUsers"].forEach(function(k){(!h[k]||w[k]>h[k])&&(h[k]=w[k])})})},function(m){e.loading=!1,e.error=m.data&&m.data.error||"Failed to load overview"})}y();var g=r(y,3e4);e.on("dispose",function(){r.cancel(g)})};var Ga=[{path:"/connections",template:"partials/connections.htm",title:"GitHub connections \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Ma(e,t.http)},{path:"/signin",template:"partials/signin.htm",title:"Sign in \u2013 Anonymous GitHub",preserveExplorer:!1,setup:()=>{}},{path:"/",template:"partials/home.htm",title:"Anonymous GitHub \u2013 Share the code, not the author",preserveExplorer:!1,setup:(e,t)=>Ta(e,t.http,t.location,t.window,t.timeout)},{path:"/dashboard",template:"partials/dashboard.htm",title:"Your anonymizations \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Oa(e,t.http,t.location,t.promises,t.window,t.quotaService)},{path:"/pr-dashboard",redirect:"/dashboard"},{path:"/anonymize/:repoId?",template:"partials/anonymize.htm",title:"New anonymization \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/pull-request-anonymize/:pullRequestId?",template:"partials/anonymize.htm",title:"Anonymize a pull request \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/gist-anonymize/:gistId?",template:"partials/anonymize.htm",title:"Anonymize a gist \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/status/:repoId",template:"partials/status.htm",title:"Repository status \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Aa(e,t.http,t.params)},{path:"/conferences",template:"partials/conferences.htm",title:"Your conferences \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Pa(e,t.http,t.location)},{path:"/con Line truncated
anonymous_github`)])])])])]),f(" Limitations "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-exclamation-triangle mr-2"}),v("Limitations ")]),s("div",{class:"panel-group",id:"faq-limitations",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingLimitation"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-limitations",href:"#limitation","aria-expanded":"false","aria-controls":"limitation"}," What are the limitations of Anonymous GitHub? ")])]),s("div",{id:"limitation",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingLimitation"},[s("div",{class:"panel-body p-3"},[s("ul",null,[s("li",null," Anonymous GitHub only anonymizes text-based files. Binary files (compiled executables, archives, etc.) are served as-is without anonymization. "),s("li",null," Files larger than 8 MB are not supported. "),s("li",null," Static site generators (such as Jekyll) used with GitHub Pages are not fully supported, although Markdown files are converted to HTML. "),s("li",null," The anonymization of terms within source code may change the behavior of the program (e.g., if a replaced term appears in a string literal or identifier). ")])])])])]),f(" Privacy & Security "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-shield-alt mr-2"}),v("Privacy & Security ")]),s("div",{class:"panel-group",id:"faq-privacy",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingData"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#data","aria-expanded":"false","aria-controls":"data"}," How is my data handled? ")])]),s("div",{id:"data",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingData"},[s("div",{class:"panel-body p-3"},[s("p",null," Data stored on Anonymous GitHub is never shared or used for any purpose beyond providing the anonymization service. When a repository is removed or expires, only its configuration is retained \u2014 this makes it easy to restore the repository if needed and ensures that no future repository reuses the same ID. ")])])]),s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingPermissions"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#permissions","aria-expanded":"false","aria-controls":"permissions"}," Why does GitHub say Anonymous GitHub asks for write access? ")])]),s("div",{id:"permissions",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingPermissions"},[s("div",{class:"panel-body p-3"},[s("p",null," Anonymous GitHub only reads your repositories \u2014 it never pushes commits, opens issues, modifies settings, or deletes anything. From your perspective as a user, the service is read-only. "),s("p",null,[v(" However, GitHub's OAuth scopes do not offer a read-only option for private repositories: the only scope that grants access to private repos is "),s("code",null,"repo"),v(", which is documented as full read/write access. To support users who want to anonymize a private repository, Anonymous GitHub must request that scope, and GitHub then displays the broader permission text at sign-in. The application itself only ever performs read operations against the GitHub API. ")]),s("p",null,[v(" If you only anonymize public repositories, the source code is open and can be audited on the "),s("a",{href:"https://github.com/tdurieux/anonymous_github/",target:"_blank"},"GitHub repository"),v(", or you can self-host your own instance. ")])])])]),s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingViewer"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#viewer","aria-expanded":"false","aria-controls":"viewer"}," Can repository owners see who viewed their anonymized repository? ")])]),s("div",{id:"viewer",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingViewer"},[s("div",{class:"panel-body p-3"},[s("p",null," No. Only the total number of views is tracked as an incremental counter. It is not possible for the repository owner or for Anonymous GitHub to identify individual viewers. Reviewer anonymity is fully preserved. ")])])])]),f(" Self-Hosting "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-server mr-2"}),v("Self-Hosting ")]),s("div",{class:"panel-group",id:"faq-hosting",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingDeploy"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-ho Line truncated
`)],{type:"text/csv"}),N=document.createElement("a");N.href=URL.createObjectURL(T),N.download=r.username+"-repositories.csv",N.click()},e.showStatusMessage=p=>{let g=p.statusMessage||"(no message)";window.prompt(`Status message for ${p.repoId} (${p.status}):`,g)},e.fetchGithubInfo=p=>{let g=window.open("","_blank");g&&g.document.write("<pre>Loading GitHub info for "+p.repoId+"...</pre>"),t.get("/api/admin/repos/"+p.repoId+"/github").then(w=>{g&&(g.document.open(),g.document.write('<pre style="font:13px monospace;padding:16px;white-space:pre-wrap">'+JSON.stringify(w.data,null,2).replace(/[<>]/g,b=>b==="<"?"&lt;":"&gt;")+"</pre>"),g.document.close())},w=>{let b=w&&w.data?JSON.stringify(w.data,null,2):String(w);g&&(g.document.body.innerHTML='<pre style="color:#B42318;padding:16px">'+b+"</pre>")})};function f(p){t.get("/api/admin/users/"+p+"/repos",{}).then(g=>{e.repositories=g.data},g=>{console.error(g)})}function h(p){t.get("/api/admin/users/"+p,{}).then(g=>{e.userInfo=g.data},g=>{console.error(g)})}h(r.username),f(r.username),e.banUser=()=>{confirm(`Ban user ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/ban`).then(()=>h(r.username),p=>console.error(p))},e.activateUser=()=>{t.post(`/api/admin/users/${r.username}/activate`).then(()=>h(r.username),p=>console.error(p))},e.promoteUser=()=>{confirm(`Promote ${r.username} to admin?`)&&t.post(`/api/admin/users/${r.username}/promote`).then(()=>h(r.username),p=>console.error(p))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${r.username}?`)&&t.post(`/api/admin/users/${r.username}/demote`).then(()=>h(r.username),p=>console.error(p))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function v(){t.get("/api/admin/tokens").then(p=>{e.tokens=p.data||[]},p=>{p.status!==401&&p.status!==403&&console.error(p)})}v(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(p=>{e.tokenForm.plaintext=p.data.token,e.tokenForm.name="",v()},p=>console.error(p))},e.revokeToken=p=>{confirm(`Revoke token "${p.name}"?`)&&t.delete("/api/admin/tokens/"+p.id).then(()=>v(),g=>console.error(g))},e.removeCache=p=>{confirm("Remove cached files for "+p.repoId+"?")&&t.delete("/api/admin/repos/"+p.repoId).then(()=>f(r.username),g=>console.error(g))},e.removeRepository=p=>{confirm("Remove repository "+p.repoId+"?")&&t.delete("/api/repo/"+p.repoId+"/").then(()=>f(r.username),g=>console.error(g))},e.updateRepository=p=>{let g=ze({title:`Refreshing ${p.repoId}...`,date:new Date,body:`The repository ${p.repoId} is going to be refreshed.`});e.toasts.push(g),t.post(`/api/repo/${p.repoId}/refresh`).then(w=>{w.data.status=="ready"?g.title=`${p.repoId} is refreshed.`:g.title=`Refreshing of ${p.repoId}.`},w=>{g.title=`Error during the refresh of ${p.repoId}.`,g.body=w.body})},e.getGitHubRepositories=p=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(g=>{e.userInfo.repositories=g.data})};let k=null;e.watch("query",()=>{n.timeout.cancel(k),k=n.timeout(()=>{f(r.username)},500)},!0)},za=function(e,t,o){let r=mt(),n=as();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let i=w=>{if(w.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){w.preventDefault();let b=document.querySelector('.admin-filter-toolbar input[type="search"]');b&&b.focus()}};n(document,"keydown",i),e.on("dispose",()=>document.removeEventListener("keydown",i)),e.clearFilter=w=>{w==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[w]="",e.query.page=1},e.chips=[];let a=()=>{let w=[];(e.query.dateFrom||e.query.dateTo)&&w.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=w};e.statusCountFor=w=>{let b=(e.statusCounts||[]).find(T=>T._id===w);return b?b.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=w=>{e.query.sort===w?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=w,e.query.direction="desc"),e.query.page=1},e.sortIcon=w=>e.query.sort===w?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let u="admin.conferences.filterPrefs",f={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},h=loadFilterPrefs(u)||{};e.query=Object.assign({},f,h,{page:1,search:""});let v=o.search();v.search&&(e.query.search=v.search);let k="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(k)||"[]"),e.savePreset=()=>{let w=window.prompt("Preset name:");if(!w)return;let b=Object.assign({},e.query);delete b.page,e.presets=(e.presets||[]).filter(T=>T.name!==w),e.presets.push({name:w,query:b}),localStorage.setItem(k,JSON.stringify(e.presets))},e.applyPreset=w=>{Object.assign(e.query,w.query,{page:1})},e.deletePreset=w=>{e.presets=(e.presets||[]).filter(b=>b.name!==w.name),localStorage.setItem(k,JS Line truncated
`).map((he,me)=>me===0?he:ke+he).join(`
`)}else typeof K=="number"||typeof K=="boolean"?ue=String(K):ue=JSON.stringify(K);x.push(`${se}${ue}${re}`)}),x.push("}"),x.join(`
`)}function h(N){let R=[],I="",C=/(\w+):(>=|<=|!=|>|<|=)?([^\s]+)/g,A=0,P;for(;P=C.exec(N);)I+=N.slice(A,P.index),A=C.lastIndex,R.push({key:P[1],op:P[2]||"=",val:P[3]});return I+=N.slice(A),{filters:R,free:I.trim().toLowerCase()}}function v(N,R){for(let I of R.filters){let C=(P,x,Q)=>{let K=parseFloat(P),z=parseFloat(x);return Q==="="?String(P)===String(x):Q==="!="?String(P)!==String(x):Q===">="?K>=z:Q==="<="?K<=z:Q===">"?K>z:Q==="<"?K<z:!0},A;if(I.key==="code")A=N.displayMessage;else if(I.key==="module")A=N.module;else if(I.key==="status")A=N._status;else if(I.key==="url")A=N._url;else if(I.key==="repo")A=N._repoId;else if(I.key==="level")A=N.level;else continue;if(A==null||!C(A,I.val,I.op))return!1}return!(R.free&&((N.displayMessage||"")+" "+(N.module||"")+" "+(N._url||"")+" "+JSON.stringify(N.raw||[])).toLowerCase().indexOf(R.free)===-1)}function k(){let N=h(e.query.search||"");e.parsedFilterCount=N.filters.length;let R=e.query.bucket,I=e.entries.filter(A=>R&&A._bucket!==R?!1:v(A,N)),C=e.query.group;if(C){let A=Q=>C==="module"?Q.module:Q.displayMessage||Q.message||"_",P=new Map;for(let Q of I){let K=A(Q);if(P.has(K)){let z=P.get(K);z.count++,z._related.push(Q),new Date(Q.ts)>new Date(z.ts)&&(z.ts=Q.ts,z._url=Q._url,z._status=Q._status),new Date(Q.ts)<new Date(z._firstSeen)&&(z._firstSeen=Q.ts)}else{let z=Object.assign({},Q);z._key=`${C}:${K}`,z._related=[Q],z._firstSeen=Q.ts,z._lastHourCount=0,z.count=1,P.set(K,z)}}let x=Date.now()-3600*1e3;for(let Q of P.values())Q._lastHourCount=Q._related.filter(K=>new Date(K.ts).getTime()>=x).length;I=Array.from(P.values())}else I=I.map((A,P)=>(A._key="row:"+P+":"+A.ts,A._related=[A],A._firstSeen=A.ts,A._lastHourCount=0,A.count=1,A));e.query.sort==="count"?I.sort((A,P)=>P.count-A.count||new Date(P.ts)-new Date(A.ts)):I.sort((A,P)=>new Date(P.ts)-new Date(A.ts)),e.visible=I}function p(N){let R=N?e.entries.length:0,I=N?e.pageSize:Math.max(e.pageSize,e.entries.length||e.pageSize);t.get("/api/admin/errors",{params:{offset:R,limit:I}}).then(C=>{let A=(C.data.entries||[]).map(u);e.entries=N?e.entries.concat(A):A,e.available=!!C.data.available,e.cap=C.data.max||e.cap,e.total=C.data.total||e.entries.length,k()},C=>console.error(C))}e.loadMore=()=>p(!0),e.canLoadMore=()=>e.entries.length<e.total;function g(){t.get("/api/admin/errors/stats").then(N=>{let R=N.data||{},I=R.prev24h?Math.round((R.last24h-R.prev24h)/R.prev24h*100):0;e.stats={last24h:R.last24h||0,prev24h:R.prev24h||0,delta:I,severity:R.severity||{error:0,warn:0,info:0},unique:R.unique||{error:0,warn:0,info:0},buckets:R.buckets||[],dropped:R.dropped||0}},N=>console.error(N))}function w(){p(),g()}e.barPx=(N,R)=>{let I=e.stats.buckets||[],C=0;for(let Q of I)C=Math.max(C,(Q.error||0)+(Q.warn||0)+(Q.info||0));if(!C)return 0;let A=(N.error||0)+(N.warn||0)+(N.info||0);if(!A)return 0;let P=Math.round(A/C*60),x=N[R]||0;return Math.round(x/A*P)},e.bucketTitle=N=>`${new Date(N.hour).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})} \xB7 ${N.error||0} err \xB7 ${N.warn||0} warn \xB7 ${N.info||0} info`,e.toggle=N=>{e.expanded[N._key]=!e.expanded[N._key]},e.setBucket=N=>{e.query.bucket=N},e.refreshNow=w,e.clearAll=()=>{confirm("Clear all captured errors?")&&t.delete("/api/admin/errors").then(w,N=>console.error(N))},e.exportCsv=()=>{let N=["ts","level","module","displayMessage","_status","_url","_repoId"],R=[N.join(",")];for(let P of e.visible)R.push(N.map(x=>{let Q=P[x]==null?"":String(P[x]);return/[",\n]/.test(Q)?`"${Q.replace(/"/g,'""')}"`:Q}).join(","));let I=new Blob([R.join(`
`)],{type:"text/csv;charset=utf-8"}),C=URL.createObjectURL(I),A=document.createElement("a");A.href=C,A.download=`errors-${new Date().toISOString().slice(0,19)}.csv`,document.body.appendChild(A),A.click(),document.body.removeChild(A),URL.revokeObjectURL(C)};function b(N){e.copyHint=`${N} copied`,n.timeout(()=>{e.copyHint=""},1500)}e.copyJson=N=>{navigator.clipboard.writeText(N._detailJson).then(()=>b("JSON"))},e.copyCurl=N=>{if(!N._url)return;let I=`curl -X ${N._method||"GET"} '${window.location.origin}${N._url}'`;navigator.clipboard.writeText(I).then(()=>b("curl"))},w();let T=r(()=>{e.query.autoRefresh&&w()},15e3);e.on("dispose",()=>r.cancel(T)),e.watch("query.search",k),e.watch("query.bucket",k),e.watch("query.sort",k),e.watch("query.group",k)},Ga=function(e,t,o,r){if(e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null){o.url("/");return}e.data=null,e.loading=!0,e.error=null;function n(p){if(p==null)return"\u2014";for(var g=["B","KB","MB","GB","TB"],w=0,b=p;b>=1024&&w<g.length-1;)b/=1024,w++;return b.toFixed(w>0?1:0)+" "+g[w]}e.humanBytes=n;function i(p){if(!p)return"\u2014";var g=Math.floor(p/86400),w=Math.floor(p%86400/3600),b=Math.floor(p%3600/60);return g>0?g+"d "+(w<10?"0":"")+w+"h":w>0?w+"h "+(b<10?"0":"")+b+"m":b+"m"}e.humanDuration=i;function a(p){return p==null?"\u2014":p>=1e6?(p/1e6).toFixed(1)+"M":p>=1e3?(p/1e3).toFixed(1)+"K":String(p)}e.humanNum=a,e.queueTotal=function(p){return p?(p.waiting||0)+(p.active||0)+(p.delayed||0)+(p.failed||0):0},e.statusCount=function(p){if(!e.data||!e.data.repos)return 0;for(var g=e.data.repos.statusBreakdown||[],w=0;w<g.length;w++)if(g[w]._id===p)return g[w].count;return 0},e.barPct=function(p){var g=e.data&&e.data.repos?e.data.repos.total:0;if(!g)return 0;var w=[p];p==="expired"&&w.push("expiring"),p==="removed"&&w.push("removing"),p==="preparing"&&w.push("download");var b=0;return w.forEach(function(T){b+=e.statusCount(T)}),Math.max(.4,b/g*100)},e.errPct=function(p){if(!e.data||!e.data.errors)return 0;var g=Math.max(e.data.errors.severity.error,e.data.errors.severity.warn,e.data.errors.severity.info,1);return e.data.errors.severity[p]/g*100};function u(p){var g=p||[];return g.map(function(w,b){var T=g[b-1]||{},N=Object.assign({},w);return N.dailyRepositories=b?Math.max(0,(w.nbRepositories||0)-(T.nbRepositories||0)):0,N.dailyUsers=b?Math.max(0,(w.nbUsers||0)-(T.nbUsers||0)):0,N.dailyPageViews=b?Math.max(0,(w.nbPageViews||0)-(T.nbPageViews||0)):0,N})}function f(p){var g=p&&p.length?p[p.length-1]:{};return{repositories:g.dailyRepositories||0,users:g.dailyUsers||0,pageViews:g.dailyPageViews||0}}var h={};e.historyBarH=function(p,g){return!p||!h[g]?0:Math.max(1,Math.round(p[g]/h[g]*140))},e.historyLabel=function(p){if(!p||!p.date)return"";var g=new Date(p.date);return g.getUTCMonth()+1+"/"+g.getUTCDate()};function v(){t.get("/api/admin/overview").then(function(p){p.data.history=u(p.data.history),p.data.daily={today:f(p.data.history)},e.data=p.data,e.loading=!1,e.error=null,h={},(p.data.history||[]).forEach(function(g){["dailyPageViews","dailyRepositories","dailyUsers","nbUsers"].forEach(function(w){(!h[w]||g[w]>h[w])&&(h[w]=g[w])})})},function(p){e.loading=!1,e.error=p.data&&p.data.error||"Failed to load overview"})}v();var k=r(v,3e4);e.on("dispose",function(){r.cancel(k)})};var ja=[{path:"/connections",template:"partials/connections.htm",title:"GitHub connections \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Ma(e,t.http)},{path:"/signin",template:"partials/signin.htm",title:"Sign in \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>$a(e,t.http)},{path:"/",template:"partials/home.htm",title:"Anonymous GitHub \u2013 Share the code, not the author",preserveExplorer:!1,setup:(e,t)=>Ta(e,t.http,t.location,t.window,t.timeout)},{path:"/dashboard",template:"partials/dashboard.htm",title:"Your anonymizations \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Oa(e,t.http,t.location,t.promises,t.window,t.quotaService)},{path:"/pr-dashboard",redirect:"/dashboard"},{path:"/anonymize/:repoId?",template:"partials/anonymize.htm",title:"New anonymization \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/pull-request-anonymize/:pullRequestId?",template:"partials/anonymize.htm",title:"Anonymize a pull request \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/gist-anonymize/:gistId?",template:"partials/anonymize.htm",title:"Anonymize a gist \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Yn(e,t.http,t.html,t.params,t.location,t.translate,t.timeout)},{path:"/status/:repoId",template:"partials/status.htm",title:"Repository status \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Aa(e,t.http,t.params)},{path:"/conferences",template:"partials/conferences.htm",title:"Your conferences \u2013 Anonymous GitHub",preserveExplorer:!1,setup:(e,t)=>Pa(e,t.http,t.location) Line truncated
anonymous_github`)])])])])]),m(" Limitations "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-exclamation-triangle mr-2"}),y("Limitations ")]),s("div",{class:"panel-group",id:"faq-limitations",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingLimitation"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-limitations",href:"#limitation","aria-expanded":"false","aria-controls":"limitation"}," What are the limitations of Anonymous GitHub? ")])]),s("div",{id:"limitation",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingLimitation"},[s("div",{class:"panel-body p-3"},[s("ul",null,[s("li",null," Anonymous GitHub only anonymizes text-based files. Binary files (compiled executables, archives, etc.) are served as-is without anonymization. "),s("li",null," Files larger than 8 MB are not supported. "),s("li",null," Static site generators (such as Jekyll) used with GitHub Pages are not fully supported, although Markdown files are converted to HTML. "),s("li",null," The anonymization of terms within source code may change the behavior of the program (e.g., if a replaced term appears in a string literal or identifier). ")])])])])]),m(" Privacy & Security "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-shield-alt mr-2"}),y("Privacy & Security ")]),s("div",{class:"panel-group",id:"faq-privacy",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingData"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#data","aria-expanded":"false","aria-controls":"data"}," How is my data handled? ")])]),s("div",{id:"data",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingData"},[s("div",{class:"panel-body p-3"},[s("p",null," Data stored on Anonymous GitHub is never shared or used for any purpose beyond providing the anonymization service. When a repository is removed or expires, only its configuration is retained \u2014 this makes it easy to restore the repository if needed and ensures that no future repository reuses the same ID. ")])])]),s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingPermissions"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#permissions","aria-expanded":"false","aria-controls":"permissions"}," Why does GitHub say Anonymous GitHub asks for write access? ")])]),s("div",{id:"permissions",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingPermissions"},[s("div",{class:"panel-body p-3"},[s("p",null," Anonymous GitHub only reads your repositories \u2014 it never pushes commits, opens issues, modifies settings, or deletes anything. From your perspective as a user, the service is read-only. "),s("p",null,[y(" However, GitHub's OAuth scopes do not offer a read-only option for private repositories: the only scope that grants access to private repos is "),s("code",null,"repo"),y(", which is documented as full read/write access. To support users who want to anonymize a private repository, Anonymous GitHub must request that scope, and GitHub then displays the broader permission text at sign-in. The application itself only ever performs read operations against the GitHub API. ")]),s("p",null,[y(" If you only anonymize public repositories, the source code is open and can be audited on the "),s("a",{href:"https://github.com/tdurieux/anonymous_github/",target:"_blank"},"GitHub repository"),y(", or you can self-host your own instance. ")])])])]),s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingViewer"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-privacy",href:"#viewer","aria-expanded":"false","aria-controls":"viewer"}," Can repository owners see who viewed their anonymized repository? ")])]),s("div",{id:"viewer",class:"panel-collapse collapse",role:"tabpanel","aria-labelledby":"headingViewer"},[s("div",{class:"panel-body p-3"},[s("p",null," No. Only the total number of views is tracked as an incremental counter. It is not possible for the repository owner or for Anonymous GitHub to identify individual viewers. Reviewer anonymity is fully preserved. ")])])])]),m(" Self-Hosting "),s("h4",{class:"faq-cat"},[s("i",{class:"fas fa-server mr-2"}),y("Self-Hosting ")]),s("div",{class:"panel-group",id:"faq-hosting",role:"tablist","aria-multiselectable":"true"},[s("div",{class:"panel panel-default mb-3"},[s("div",{class:"panel-heading p-3",role:"tab",id:"headingDeploy"},[s("h3",{class:"panel-title"},[s("a",{class:"collapsed",role:"button","data-toggle":"collapse","data-parent":"#faq-ho Line truncated
anonymized repository often think it is about the paper, so the
label names the service and the tooltip says who to contact. `),t[12]||(t[12]=s("li",{class:"nav-item d-none d-lg-block"},[s("a",{class:"nav-link",target:"_blank",rel:"noopener",href:"https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml",title:"Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors.","data-offset":"30"},[s("i",{class:"fas fa-bug d-lg-none mr-1","aria-hidden":"true"}),v("Report a bug ")])],-1)),e.isDarkMode?f("v-if",!0):(l(),d("li",BO,[s("a",{class:"nav-link nav-icon",href:"#",title:"Switch to dark mode","aria-label":"Switch to dark mode",role:"button",onClick:t[0]||(t[0]=ke(o=>{e.darkMode(!0)},["prevent"]))},[...t[7]||(t[7]=[s("i",{class:"fas fa-moon","aria-hidden":"true"},null,-1)])])])),e.isDarkMode?(l(),d("li",GO,[s("a",{class:"nav-link nav-icon",href:"#",title:"Switch to light mode","aria-label":"Switch to light mode",role:"button",onClick:t[1]||(t[1]=ke(o=>{e.darkMode(!1)},["prevent"]))},[...t[8]||(t[8]=[s("i",{class:"fas fa-sun","aria-hidden":"true"},null,-1)])])])):f("v-if",!0),e.user?f("v-if",!0):(l(),d("li",jO,[...t[9]||(t[9]=[s("a",{class:"nav-link btn-signin",target:"_self",href:"/signin","data-offset":"30"},[s("i",{class:"fab fa-github mr-1"}),v(" Sign in ")],-1)])])),e.user?(l(),d("li",WO,[s("a",KO,[e.user?.photo?(l(),d("img",{key:0,width:"22",height:"22",class:"rounded-circle",src:e.safeUrl(e.user?.photo)},null,8,YO)):f("v-if",!0),s("span",JO,c(e.user?.username),1)]),s("div",QO,[s("h6",XO,"Signed in as @"+c(e.user?.username),1),t[10]||(t[10]=ge('<a class="dropdown-item" href="/profile"><i class="fas fa-sliders-h" aria-hidden="true"></i><span>Settings<small>Defaults, quotas, account</small></span></a><a class="dropdown-item" href="/connections"><i class="fab fa-github" aria-hidden="true"></i><span>GitHub connections<small>Repository access and OAuth</small></span></a><a class="dropdown-item" href="/claim"><i class="fas fa-link" aria-hidden="true"></i><span>Claim an anonymization<small>Attach an existing mirror to your account</small></span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><i class="fas fa-bug" aria-hidden="true"></i><span>Report a bug<small>About this service, not a paper</small></span></a><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener"><i class="fab fa-github" aria-hidden="true"></i><span>Source code</span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="/api/user/logout" target="_self"><i class="fas fa-sign-out-alt" aria-hidden="true"></i><span>Sign out</span></a>',8))])])):f("v-if",!0)])])],2),e.generalMessage?(l(),d("div",{key:0,class:"navbar shadow generalMessage",innerHTML:e.sanitize(e.generalMessage)},null,8,ZO)):f("v-if",!0)],64)}var xO={class:"paper-landing"},eA={class:"paper-hero"},tA={class:"paper-hero-copy"},sA={class:"paper-cta-row"},nA={key:0,href:"/signin",target:"_self",class:"btn-hero"},oA={key:1,href:"/anonymize",class:"btn-hero"},rA={class:"paper-stats paper-wrap",id:"metrics","aria-label":"Usage over the last 60 days"},iA={class:"paper-stats-grid"},aA={class:"paper-stat-value"},lA={class:"paper-stat-label"},dA=["viewBox"],uA=["x","y","width","height"],cA={key:1,class:"paper-stat-delta"},pA={class:"paper-features paper-wrap","aria-label":"Product features"},fA={class:"paper-features-list",role:"tablist","aria-label":"Features","aria-orientation":"vertical"},mA=["onClick","onKeydown","id","aria-controls","aria-selected","tabindex"],hA={class:"paper-eyebrow"},vA={class:"paper-feature-title"},yA={class:"paper-feature-more"},gA={class:"paper-feature-title paper-feature-title-m","aria-hidden":"true"},bA=["href","target"],wA={class:"paper-features-stage"},kA=["id","aria-labelledby"],_A={class:"paper-frame"},EA={class:"paper-frame-bar","aria-hidden":"true"},CA={class:"url"},NA=["src","alt"],SA={class:"paper-footer"},DA={class:"paper-footer-inner"},RA={class:"paper-footer-col"},TA={key:0,href:"/dashboard"},OA={key:1,href:"/conferences"},AA={class:"paper-footer-foot"};function cl(e,t){return l(),d("div",xO,[f(" Hero: copy on the left, the product on the right "),s("section",eA,[s("div",tA,[t[2]||(t[2]=s("div",{class:"paper-eyebrow"},"For double-blind peer review",-1)),t[3]||(t[3]=s("h1",{class:"hero-title"},[v(" Share the code,"),s("br"),s("span",{class:"accent"},"not the author.")],-1)),t[4]||(t[4]=s("p",{class:"hero-subtitle"}," Anonymous GitHub makes a read-only mirror of your repository, pull request, or gist with every trace of identity removed, so you can attach a link to your submission without breaking anonymity. ",-1)),s("div",sA,[e.user?f("v-if",!0):(l(),d("a",nA,[...t[0]||(t[0]=[s("i",{class:"fab fa-github mr-2","aria-hidden":"true"},null,-1),v("Sign in with GitHub ",-1)])])),e.user?(l(),d("a",oA," Anonymize a repository \ Line truncated
label names the service and the tooltip says who to contact. `),t[12]||(t[12]=s("li",{class:"nav-item d-none d-lg-block"},[s("a",{class:"nav-link",target:"_blank",rel:"noopener",href:"https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml",title:"Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors.","data-offset":"30"},[s("i",{class:"fas fa-bug d-lg-none mr-1","aria-hidden":"true"}),y("Report a bug ")])],-1)),e.isDarkMode?m("v-if",!0):(l(),d("li",YO,[s("a",{class:"nav-link nav-icon",href:"#",title:"Switch to dark mode","aria-label":"Switch to dark mode",role:"button",onClick:t[0]||(t[0]=we(o=>{e.darkMode(!0)},["prevent"]))},[...t[7]||(t[7]=[s("i",{class:"fas fa-moon","aria-hidden":"true"},null,-1)])])])),e.isDarkMode?(l(),d("li",JO,[s("a",{class:"nav-link nav-icon",href:"#",title:"Switch to light mode","aria-label":"Switch to light mode",role:"button",onClick:t[1]||(t[1]=we(o=>{e.darkMode(!1)},["prevent"]))},[...t[8]||(t[8]=[s("i",{class:"fas fa-sun","aria-hidden":"true"},null,-1)])])])):m("v-if",!0),e.user?m("v-if",!0):(l(),d("li",QO,[...t[9]||(t[9]=[s("a",{class:"nav-link btn-signin",target:"_self",href:"/signin","data-offset":"30"},[s("i",{class:"fab fa-github mr-1"}),y(" Sign in ")],-1)])])),e.user?(l(),d("li",XO,[s("a",ZO,[e.user?.photo?(l(),d("img",{key:0,width:"22",height:"22",class:"rounded-circle",src:e.safeUrl(e.user?.photo)},null,8,xO)):m("v-if",!0),s("span",eA,c(e.user?.username),1)]),s("div",tA,[s("h6",sA,"Signed in as @"+c(e.user?.username),1),t[10]||(t[10]=ge('<a class="dropdown-item" href="/profile"><i class="fas fa-sliders-h" aria-hidden="true"></i><span>Settings<small>Defaults, quotas, account</small></span></a><a class="dropdown-item" href="/connections"><i class="fab fa-github" aria-hidden="true"></i><span>GitHub connections<small>Repository access and OAuth</small></span></a><a class="dropdown-item" href="/claim"><i class="fas fa-link" aria-hidden="true"></i><span>Claim an anonymization<small>Attach an existing mirror to your account</small></span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><i class="fas fa-bug" aria-hidden="true"></i><span>Report a bug<small>About this service, not a paper</small></span></a><a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener"><i class="fab fa-github" aria-hidden="true"></i><span>Source code</span></a><div class="dropdown-divider"></div><a class="dropdown-item" href="/api/user/logout" target="_self"><i class="fas fa-sign-out-alt" aria-hidden="true"></i><span>Sign out</span></a>',8))])])):m("v-if",!0)])])],2),e.generalMessage?(l(),d("div",{key:0,class:"navbar shadow generalMessage",innerHTML:e.sanitize(e.generalMessage)},null,8,nA)):m("v-if",!0)],64)}var oA={class:"paper-landing"},rA={class:"paper-hero"},iA={class:"paper-hero-copy"},aA={class:"paper-cta-row"},lA={key:0,href:"/signin",target:"_self",class:"btn-hero"},dA={key:1,href:"/anonymize",class:"btn-hero"},uA={class:"paper-stats paper-wrap",id:"metrics","aria-label":"Usage over the last 60 days"},cA={class:"paper-stats-grid"},pA={class:"paper-stat-value"},fA={class:"paper-stat-label"},mA=["viewBox"],hA=["x","y","width","height"],vA={key:1,class:"paper-stat-delta"},yA={class:"paper-features paper-wrap","aria-label":"Product features"},gA={class:"paper-features-list",role:"tablist","aria-label":"Features","aria-orientation":"vertical"},bA=["onClick","onKeydown","id","aria-controls","aria-selected","tabindex"],wA={class:"paper-eyebrow"},kA={class:"paper-feature-title"},_A={class:"paper-feature-more"},EA={class:"paper-feature-title paper-feature-title-m","aria-hidden":"true"},CA=["href","target"],NA={class:"paper-features-stage"},SA=["id","aria-labelledby"],DA={class:"paper-frame"},RA={class:"paper-frame-bar","aria-hidden":"true"},TA={class:"url"},OA=["src","alt"],AA={class:"paper-footer"},IA={class:"paper-footer-inner"},VA={class:"paper-footer-col"},PA={key:0,href:"/dashboard"},qA={key:1,href:"/conferences"},MA={class:"paper-footer-foot"};function pl(e,t){return l(),d("div",oA,[m(" Hero: copy on the left, the product on the right "),s("section",rA,[s("div",iA,[t[2]||(t[2]=s("div",{class:"paper-eyebrow"},"For double-blind peer review",-1)),t[3]||(t[3]=s("h1",{class:"hero-title"},[y(" Share the code,"),s("br"),s("span",{class:"accent"},"not the author.")],-1)),t[4]||(t[4]=s("p",{class:"hero-subtitle"}," Anonymous GitHub makes a read-only mirror of your repository, pull request, or gist with every trace of identity removed, so you can attach a link to your submission without breaking anonymity. ",-1)),s("div",aA,[e.user?m("v-if",!0):(l(),d("a",lA,[...t[0]||(t[0]=[s("i",{class:"fab fa-github mr-2","aria-hidden":"true"},null,-1),y("Sign in with GitHub ",-1)])])),e.user?(l(),d("a",dA," Anonymize a repository \ Line truncated
MIT CSAIL
jane.smith@example.org`},AP={id:"termsHelp",class:"form-text text-muted"},IP={class:"invalid-feedback"},VP={id:"settings-display",class:"paper-settings-section"},PP={class:"form-group paper-check-list"},qP={class:"form-check"},MP={class:"form-check-input",type:"checkbox",id:"link",name:"link"},$P={class:"form-check"},LP={class:"form-check-input",type:"checkbox",id:"image",name:"image"},FP={class:"form-check"},UP={class:"form-check-input",type:"checkbox",id:"pdf",name:"pdf"},zP={class:"form-check"},HP={class:"form-check-input",type:"checkbox",id:"notebook",name:"notebook"},BP={id:"settings-features",class:"paper-settings-section"},GP={class:"form-group paper-check-list"},jP={class:"form-check"},WP={class:"form-check-input",type:"checkbox",id:"page",name:"page"},KP={class:"form-check"},YP={class:"form-check-input",type:"checkbox",id:"loc",name:"loc"},JP={class:"form-check"},QP={class:"form-check-input",type:"checkbox",id:"update",name:"update"},XP={class:"form-group"},ZP={class:"form-control",id:"mode",name:"mode"},xP={class:"form-text text-muted"},e3={id:"settings-expiration",class:"paper-settings-section"},t3={class:"form-group"},s3={class:"form-control",id:"expiration",name:"expiration"},n3={class:"paper-settings-footer"},o3={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},r3=["textContent"],i3={class:"paper-settings-actions"},a3=["disabled"],l3={key:0,class:"fas fa-check mr-1","aria-hidden":"true"},d3={id:"settings-account",class:"paper-settings-section"},u3={class:"paper-account-card"},c3=["src"],p3={class:"paper-account-identity"},f3={class:"paper-account-name"},m3={class:"paper-danger-zone"},h3={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},v3=["textContent"],y3=["disabled"];function vl(e,t){let o=we("paper-scrollspy"),r=we("field"),n=we("form");return l(),d("div",uP,[t[48]||(t[48]=ge('<div class="paper-crumbs"><a href="/dashboard">My work</a> \xA0/\xA0 <span class="here">Settings</span></div><h1 class="paper-page-title">Your <em>settings</em></h1><p class="paper-page-lede">Defaults applied to every new anonymization, your quotas, and your account.</p>',3)),s("div",cP,[E((l(),d("aside",pP,[...t[2]||(t[2]=[ge('<div class="paper-settings-toc-head">On this page</div><nav><a href="#settings-quota">Quota</a><a href="#settings-terms">Terms to redact</a><a href="#settings-display">Display</a><a href="#settings-features">Features</a><a href="#settings-expiration">Expiration</a><a href="#settings-account">Account</a></nav>',2)])])),[[o]]),s("div",fP,[f(" Quota: same markup as the dashboard, driven by quotaService "),s("section",mP,[t[4]||(t[4]=s("div",{class:"paper-section-eyebrow"},"Quota",-1)),t[5]||(t[5]=s("p",{class:"paper-section-copy"},"What you are using across all your anonymizations. Conferences can lift these limits during a review window.",-1)),e.quota?(l(),d("div",hP,[(l(),d(Y,null,oe([{key:"repository",label:"Repositories",kind:"count"},{key:"storage",label:"Storage",kind:"bytes"},{key:"file",label:"Files",kind:"count"}],(i,a)=>s("div",vP,[s("div",yP,[s("span",gP,c(i?.label),1),i?.kind==="count"?(l(),d("span",bP,[v(c(e.fmt?.number(e.quota[i?.key].used)),1),e.quota[i?.key].unlimited?f("v-if",!0):(l(),d("span",wP," / "+c(e.fmt?.number(e.quota[i?.key].total)),1)),e.quota[i?.key].unlimited?(l(),d("span",kP,"Unlimited")):f("v-if",!0)])):f("v-if",!0),i?.kind==="bytes"?(l(),d("span",_P,[v(c(e.fmt?.humanFileSize(e.quota[i?.key].used)),1),e.quota[i?.key].unlimited?f("v-if",!0):(l(),d("span",EP," / "+c(e.fmt?.humanFileSize(e.quota[i?.key].total)),1)),e.quota[i?.key].unlimited?(l(),d("span",CP,"Unlimited")):f("v-if",!0)])):f("v-if",!0)]),s("div",{class:O(["quota-track","quota-"+e.quota[i?.key].level]),role:"progressbar","aria-valuemin":"0","aria-label":i?.label+" quota","aria-valuenow":e.quota[i?.key].used,"aria-valuemax":e.quota[i?.key].unlimited?e.quota[i?.key].used:e.quota[i?.key].total,"aria-valuetext":e.quota[i?.key].unlimited?"unlimited":e.fmt.number(e.quota[i?.key].percent,0)+"% used"},[e.quota[i?.key].unlimited?f("v-if",!0):(l(),d("div",{key:0,class:"quota-fill",style:Ve({width:e.quota[i?.key].percent+"%"})},null,4))],10,NP)])),64))])):f("v-if",!0),e.quota?f("v-if",!0):(l(),d("div",SP,[(l(),d(Y,null,oe([1,2,3],(i,a)=>s("div",DP,[...t[3]||(t[3]=[s("div",{class:"quota-header"},[s("span",{class:"skeleton skeleton-line",style:{width:"40%"}}),s("span",{class:"skeleton skeleton-line",style:{width:"25%"}})],-1),s("div",{class:"quota-track"},null,-1)])])),64))]))]),E((l(),d("form",{class:"form needs-validation",name:"defaultsForm",novalidate:"",onSubmit:t[0]||(t[0]=ke(i=>e.submitForm(i,()=>{e.saveDefault(i)}),["prevent"]))},[s("section",RP,[t[11]||(t[11]=s("div",{class:"paper-section-eyebrow"},"Terms to redact",-1)),t[12]||(t[12]=s("p",{class:"paper-section-copy"},"Pre-filled in every new anonymization. You can still edit the list per anonymization.",-1)),s("div",TP,[t[10]||(t[10]=s("label",{class:"paper-field-label",for:"terms"},"Default terms",-1)),E(s("tex Line truncated
`);for(let g=0;g<y.length;g++){let m=y[g];if(m.startsWith("diff --git")){h(),p();continue}if(m.startsWith("--- ")){i&&i.lines.length&&h(),p().oldPath=m.replace(/^--- (a\/)?/,"").trim();continue}if(m.startsWith("+++ ")){p().newPath=m.replace(/^\+\+\+ (b\/)?/,"").trim();continue}if(!(m.startsWith("index ")||m.startsWith("similarity index")||m.startsWith("rename ")||m.startsWith("new file mode")||m.startsWith("deleted file mode")||m.startsWith("Binary files"))){if(m.startsWith("@@")){let w=m.match(/@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);w&&(a=parseInt(w[1],10),u=parseInt(w[2],10)),p().lines.push({kind:"hunk",oldNo:"",newNo:"",text:m});continue}i&&(m.startsWith("+")?(i.lines.push({kind:"add",oldNo:"",newNo:u,text:m.slice(1)}),u++):m.startsWith("-")?(i.lines.push({kind:"remove",oldNo:a,newNo:"",text:m.slice(1)}),a++):(i.lines.push({kind:"ctx",oldNo:a,newNo:u,text:m.startsWith(" ")?m.slice(1):m}),a++,u++))}}return o(n,i),n.join("")}})({trustAsHtml:e=>e});function Rq(e,t){return e==null?"":Number(e).toLocaleString(void 0,t==null?{}:{minimumFractionDigits:t,maximumFractionDigits:t})}function Tq(e){return String(e??"").toUpperCase()}function Oq(e,t,o=0){return(e||[]).slice(o,o+t)}function Aq(e,t){return Array.isArray(e)?typeof t=="function"?e.filter(t):e:[]}function Iq(e,t){if(!t)return e||[];let o=Array.isArray(t)?t:[t];return[...e||[]].sort((r,n)=>{for(let i of o){let a=i[0]==="-"?-1:1;i=i.replace(/^[+-]/,"");let u=i.split(".").reduce((h,y)=>h?.[y],r),p=i.split(".").reduce((h,y)=>h?.[y],n);if(typeof u=="string"&&(u=u.toLowerCase()),typeof p=="string"&&(p=p.toLowerCase()),u!==p)return(u==null?-1:p==null?1:u<p?-1:1)*a}return 0})}function Vq(e,t){if(!e)return"";let o=new Date(e);if(isNaN(o))return String(e);if(!t||t==="mediumDate")return o.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"});let r={yyyy:o.getFullYear(),MMM:o.toLocaleDateString(void 0,{month:"short"}),MM:String(o.getMonth()+1).padStart(2,"0"),dd:String(o.getDate()).padStart(2,"0"),d:o.getDate(),HH:String(o.getHours()).padStart(2,"0"),mm:String(o.getMinutes()).padStart(2,"0"),ss:String(o.getSeconds()).padStart(2,"0")};return t.replace(/yyyy|MMM|MM|dd|HH|mm|ss|d/g,n=>r[n])}function Pq(e,t){return(t?.[e]??t?.[e===1?"one":"other"]??"").replace(/\{\}/g,e??0)}var El={ERRORS:{repository_job_cancelled:"The repository changed or was removed while processing.",storage_delete_failed:"Unable to remove cached files. Please try again later.",unknown_error:"Unknown error, contact the admin.",unreachable:"Anonymous GitHub is unreachable, contact the admin.",request_error:"Unable to download the file, check your connection or contact the admin.",not_found:"The requested resource could not be found.",not_connected:"You must be logged in to perform this action.",not_authorized:"You do not have permission to perform this action.",unable_to_connect_user:"Unable to connect your account. Please try again later.",user_not_found:"The requested user could not be found.",user_banned:"Your account has been banned. Contact the admin for more information.",repo_access_limited:"GitHub blocked access because the repository's organization restricts third-party OAuth apps. Ask an org owner to approve Anonymous GitHub under Settings \u2192 Third-party Access \u2192 OAuth app policy, or anonymize a personal fork instead.",repo_saml_enforcement:"The repository's organization enforces SAML single sign-on. Authorize your token for that organization (GitHub \u2192 Settings \u2192 Applications, or re-run the org's SSO sign-in), then retry. Alternatively, anonymize a personal fork.",repo_not_found:"The repository was not found on GitHub. Check the URL and spelling, make sure you are signed in to the account that can see it, and confirm the repo isn't hidden under an org that restricts third-party app access.",repo_empty:"The selected branch has no commits on GitHub. Push at least one commit, or pick a different branch, then retry.",repo_not_accessible:"Anonymous GitHub cannot access this repository. Verify the repository exists and that Anonymous GitHub has been authorized for the owning organization.",repository_archived:"This repository has been archived and its files are no longer available.",repository_expired:"The repository is expired.",invalid_status:"This action cannot be performed while the resource is in its current state.",repository_not_ready:"Anonymous GitHub is still processing the repository, it can take several minutes.",repository_not_accessible:"This repository is currently not accessible.",repo_is_updating:"Anonymous GitHub is still processing the repository, it can take several minutes.",invalid_repo:"The provided repository is not valid.",invalid_source_repository:"The repository source name is invalid; expected the form 'owner/name'.",repoId_not_defined:"A repository ID must be provided.",repoUrl_not_defined:"The repository URL needs to be defined.",source_not_provided:"A repository source must be provided.",github_rate_limit_exceeded:"GitHub Line truncated
jane.smith@example.org`},MP={id:"termsHelp",class:"form-text text-muted"},$P={class:"invalid-feedback"},LP={id:"settings-display",class:"paper-settings-section"},UP={class:"form-group paper-check-list"},FP={class:"form-check"},zP={class:"form-check-input",type:"checkbox",id:"link",name:"link"},HP={class:"form-check"},BP={class:"form-check-input",type:"checkbox",id:"image",name:"image"},GP={class:"form-check"},jP={class:"form-check-input",type:"checkbox",id:"pdf",name:"pdf"},WP={class:"form-check"},KP={class:"form-check-input",type:"checkbox",id:"notebook",name:"notebook"},YP={id:"settings-features",class:"paper-settings-section"},JP={class:"form-group paper-check-list"},QP={class:"form-check"},XP={class:"form-check-input",type:"checkbox",id:"page",name:"page"},ZP={class:"form-check"},xP={class:"form-check-input",type:"checkbox",id:"loc",name:"loc"},e3={class:"form-check"},t3={class:"form-check-input",type:"checkbox",id:"update",name:"update"},s3={class:"form-group"},n3={class:"form-control",id:"mode",name:"mode"},o3={class:"form-text text-muted"},r3={id:"settings-expiration",class:"paper-settings-section"},i3={class:"form-group"},a3={class:"form-control",id:"expiration",name:"expiration"},l3={class:"paper-settings-footer"},d3={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},u3=["textContent"],c3={class:"paper-settings-actions"},p3=["disabled"],f3={key:0,class:"fas fa-check mr-1","aria-hidden":"true"},m3={id:"settings-account",class:"paper-settings-section"},h3={class:"paper-account-card"},v3=["src"],y3={class:"paper-account-identity"},g3={class:"paper-account-name"},b3={class:"paper-danger-zone"},w3={key:0,class:"paper-inline-alert paper-inline-alert-error",role:"alert"},k3=["textContent"],_3=["disabled"];function yl(e,t){let o=be("paper-scrollspy"),r=be("field"),n=be("form");return l(),d("div",hP,[t[48]||(t[48]=ge('<div class="paper-crumbs"><a href="/dashboard">My work</a> \xA0/\xA0 <span class="here">Settings</span></div><h1 class="paper-page-title">Your <em>settings</em></h1><p class="paper-page-lede">Defaults applied to every new anonymization, your quotas, and your account.</p>',3)),s("div",vP,[E((l(),d("aside",yP,[...t[2]||(t[2]=[ge('<div class="paper-settings-toc-head">On this page</div><nav><a href="#settings-quota">Quota</a><a href="#settings-terms">Terms to redact</a><a href="#settings-display">Display</a><a href="#settings-features">Features</a><a href="#settings-expiration">Expiration</a><a href="#settings-account">Account</a></nav>',2)])])),[[o]]),s("div",gP,[m(" Quota: same markup as the dashboard, driven by quotaService "),s("section",bP,[t[4]||(t[4]=s("div",{class:"paper-section-eyebrow"},"Quota",-1)),t[5]||(t[5]=s("p",{class:"paper-section-copy"},"What you are using across all your anonymizations. Conferences can lift these limits during a review window.",-1)),e.quota?(l(),d("div",wP,[(l(),d(Y,null,ne([{key:"repository",label:"Repositories",kind:"count"},{key:"storage",label:"Storage",kind:"bytes"},{key:"file",label:"Files",kind:"count"}],(i,a)=>s("div",kP,[s("div",_P,[s("span",EP,c(i?.label),1),i?.kind==="count"?(l(),d("span",CP,[y(c(e.fmt?.number(e.quota[i?.key].used)),1),e.quota[i?.key].unlimited?m("v-if",!0):(l(),d("span",NP," / "+c(e.fmt?.number(e.quota[i?.key].total)),1)),e.quota[i?.key].unlimited?(l(),d("span",SP,"Unlimited")):m("v-if",!0)])):m("v-if",!0),i?.kind==="bytes"?(l(),d("span",DP,[y(c(e.fmt?.humanFileSize(e.quota[i?.key].used)),1),e.quota[i?.key].unlimited?m("v-if",!0):(l(),d("span",RP," / "+c(e.fmt?.humanFileSize(e.quota[i?.key].total)),1)),e.quota[i?.key].unlimited?(l(),d("span",TP,"Unlimited")):m("v-if",!0)])):m("v-if",!0)]),s("div",{class:O(["quota-track","quota-"+e.quota[i?.key].level]),role:"progressbar","aria-valuemin":"0","aria-label":i?.label+" quota","aria-valuenow":e.quota[i?.key].used,"aria-valuemax":e.quota[i?.key].unlimited?e.quota[i?.key].used:e.quota[i?.key].total,"aria-valuetext":e.quota[i?.key].unlimited?"unlimited":e.fmt.number(e.quota[i?.key].percent,0)+"% used"},[e.quota[i?.key].unlimited?m("v-if",!0):(l(),d("div",{key:0,class:"quota-fill",style:Pe({width:e.quota[i?.key].percent+"%"})},null,4))],10,OP)])),64))])):m("v-if",!0),e.quota?m("v-if",!0):(l(),d("div",AP,[(l(),d(Y,null,ne([1,2,3],(i,a)=>s("div",IP,[...t[3]||(t[3]=[s("div",{class:"quota-header"},[s("span",{class:"skeleton skeleton-line",style:{width:"40%"}}),s("span",{class:"skeleton skeleton-line",style:{width:"25%"}})],-1),s("div",{class:"quota-track"},null,-1)])])),64))]))]),E((l(),d("form",{class:"form needs-validation",name:"defaultsForm",novalidate:"",onSubmit:t[0]||(t[0]=we(i=>e.submitForm(i,()=>{e.saveDefault(i)}),["prevent"]))},[s("section",VP,[t[11]||(t[11]=s("div",{class:"paper-section-eyebrow"},"Terms to redact",-1)),t[12]||(t[12]=s("p",{class:"paper-section-copy"},"Pre-filled in every new anonymization. You can still edit the list per anonymization.",-1)),s("div",PP,[t[10]||(t[10]=s("label",{class:"paper-field-label",for:"terms"},"Default terms",-1)),E(s("tex Line truncated
`);for(let k=0;k<v.length;k++){let p=v[k];if(p.startsWith("diff --git")){h(),f();continue}if(p.startsWith("--- ")){i&&i.lines.length&&h(),f().oldPath=p.replace(/^--- (a\/)?/,"").trim();continue}if(p.startsWith("+++ ")){f().newPath=p.replace(/^\+\+\+ (b\/)?/,"").trim();continue}if(!(p.startsWith("index ")||p.startsWith("similarity index")||p.startsWith("rename ")||p.startsWith("new file mode")||p.startsWith("deleted file mode")||p.startsWith("Binary files"))){if(p.startsWith("@@")){let g=p.match(/@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);g&&(a=parseInt(g[1],10),u=parseInt(g[2],10)),f().lines.push({kind:"hunk",oldNo:"",newNo:"",text:p});continue}i&&(p.startsWith("+")?(i.lines.push({kind:"add",oldNo:"",newNo:u,text:p.slice(1)}),u++):p.startsWith("-")?(i.lines.push({kind:"remove",oldNo:a,newNo:"",text:p.slice(1)}),a++):(i.lines.push({kind:"ctx",oldNo:a,newNo:u,text:p.startsWith(" ")?p.slice(1):p}),a++,u++))}}return o(n,i),n.join("")}})({trustAsHtml:e=>e});function Vq(e,t){return e==null?"":Number(e).toLocaleString(void 0,t==null?{}:{minimumFractionDigits:t,maximumFractionDigits:t})}function Pq(e){return String(e??"").toUpperCase()}function qq(e,t,o=0){return(e||[]).slice(o,o+t)}function Mq(e,t){return Array.isArray(e)?typeof t=="function"?e.filter(t):e:[]}function $q(e,t){if(!t)return e||[];let o=Array.isArray(t)?t:[t];return[...e||[]].sort((r,n)=>{for(let i of o){let a=i[0]==="-"?-1:1;i=i.replace(/^[+-]/,"");let u=i.split(".").reduce((h,v)=>h?.[v],r),f=i.split(".").reduce((h,v)=>h?.[v],n);if(typeof u=="string"&&(u=u.toLowerCase()),typeof f=="string"&&(f=f.toLowerCase()),u!==f)return(u==null?-1:f==null?1:u<f?-1:1)*a}return 0})}function Lq(e,t){if(!e)return"";let o=new Date(e);if(isNaN(o))return String(e);if(!t||t==="mediumDate")return o.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"});let r={yyyy:o.getFullYear(),MMM:o.toLocaleDateString(void 0,{month:"short"}),MM:String(o.getMonth()+1).padStart(2,"0"),dd:String(o.getDate()).padStart(2,"0"),d:o.getDate(),HH:String(o.getHours()).padStart(2,"0"),mm:String(o.getMinutes()).padStart(2,"0"),ss:String(o.getSeconds()).padStart(2,"0")};return t.replace(/yyyy|MMM|MM|dd|HH|mm|ss|d/g,n=>r[n])}function Uq(e,t){return(t?.[e]??t?.[e===1?"one":"other"]??"").replace(/\{\}/g,e??0)}var Cl={ERRORS:{repository_job_cancelled:"The repository changed or was removed while processing.",storage_delete_failed:"Unable to remove cached files. Please try again later.",unknown_error:"Unknown error, contact the admin.",unreachable:"Anonymous GitHub is unreachable, contact the admin.",request_error:"Unable to download the file, check your connection or contact the admin.",not_found:"The requested resource could not be found.",not_connected:"You must be logged in to perform this action.",not_authorized:"You do not have permission to perform this action.",unable_to_connect_user:"Unable to connect your account. Please try again later.",user_not_found:"The requested user could not be found.",user_banned:"Your account has been banned. Contact the admin for more information.",repo_access_limited:"GitHub blocked access because the repository's organization restricts third-party OAuth apps. Ask an org owner to approve Anonymous GitHub under Settings \u2192 Third-party Access \u2192 OAuth app policy, or anonymize a personal fork instead.",repo_saml_enforcement:"The repository's organization enforces SAML single sign-on. Authorize your token for that organization (GitHub \u2192 Settings \u2192 Applications, or re-run the org's SSO sign-in), then retry. Alternatively, anonymize a personal fork.",repo_not_found:"The repository was not found on GitHub. Check the URL and spelling, make sure you are signed in to the account that can see it, and confirm the repo isn't hidden under an org that restricts third-party app access.",repo_empty:"The selected branch has no commits on GitHub. Push at least one commit, or pick a different branch, then retry.",repo_not_accessible:"Anonymous GitHub cannot access this repository. Verify the repository exists and that Anonymous GitHub has been authorized for the owning organization.",repository_archived:"This repository has been archived and its files are no longer available.",repository_expired:"The repository is expired.",invalid_status:"This action cannot be performed while the resource is in its current state.",repository_not_ready:"Anonymous GitHub is still processing the repository, it can take several minutes.",repository_not_accessible:"This repository is currently not accessible.",repo_is_updating:"Anonymous GitHub is still processing the repository, it can take several minutes.",invalid_repo:"The provided repository is not valid.",invalid_source_repository:"The repository source name is invalid; expected the form 'owner/name'.",repoId_not_defined:"A repository ID must be provided.",repoUrl_not_defined:"The repository URL needs to be defined.",source_not_provided:"A repository source must be provided.",github_rate_limit_exceeded:"GitHub Line truncated
/*! Bundled license information:
@vue/shared/dist/shared.esm-bundler.js:
+15
View File
@@ -20,3 +20,18 @@ export function getLoginToken(
}
return null;
}
export function safeAuthReturnTo(value: unknown, fallback = "/dashboard"): string {
return typeof value === "string" && /^\/(?:(?:anonymize|pull-request-anonymize|gist-anonymize)(?:\/[\w-]+)?|connections|dashboard)(?:\?[^\\\r\n]*)?$/.test(value) ? value : fallback;
}
export type OAuthContext = { ownerId?: string; githubId?: string; returnTo: string; expires: number; recovery?: boolean };
declare module "express-session" {
interface SessionData {
githubRecovery?: OAuthContext;
githubOAuthFlow?: OAuthContext;
}
}
declare module "express-serve-static-core" {
interface Request { githubOAuthContext?: OAuthContext; }
}
+40 -3
View File
@@ -13,7 +13,7 @@ import AnonymousError from "../../core/AnonymousError";
import { setCredential } from "../../core/credentials";
import { hashToken } from "./token-auth";
import { createLogger, serializeError } from "../../core/logger";
import { getLoginToken, isDisabledAccount } from "./auth-utils";
import { getLoginToken, isDisabledAccount, safeAuthReturnTo } from "./auth-utils";
const logger = createLogger("auth");
@@ -28,7 +28,8 @@ export function ensureAuthenticated(
res.status(401).json({ error: "not_connected" });
}
const verify = async (
export const verify = async (
req: express.Request,
accessToken: string,
_refreshToken: string,
profile: Profile,
@@ -36,8 +37,19 @@ const verify = async (
): Promise<void> => {
let user: IUserDocument | null;
try {
const flow = req.githubOAuthContext;
const currentId = (req.user as { user?: { id?: string } } | undefined)?.user?.id;
if (!flow || flow.expires < Date.now() || (flow.githubId && flow.githubId !== profile.id)
|| (currentId && currentId !== flow.ownerId)) {
done(new AnonymousError("github_identity_mismatch", { httpStatus: 409 }));
return;
}
user = await UserModel.findOne({ "externalIDs.github": profile.id });
if (user) {
if (flow.ownerId && user.id !== flow.ownerId) {
done(new AnonymousError("github_identity_mismatch", { httpStatus: 409 }));
return;
}
if (isDisabledAccount(user.status)) {
done(
new AnonymousError(
@@ -53,6 +65,10 @@ const verify = async (
// existing account instead of creating a duplicate that would lose
// the isAdmin flag.
user = await UserModel.findOne({ username: profile.username });
if (flow.ownerId && (!user || user.id !== flow.ownerId)) {
done(new AnonymousError("github_identity_mismatch", { httpStatus: 409 }));
return;
}
if (user) {
if (user.externalIDs?.github && user.externalIDs.github !== profile.id) {
done(new AnonymousError("not_connected", { httpStatus: 401 }));
@@ -120,6 +136,7 @@ const verify = async (
if (config.GITHUB_OAUTH_ENABLED) passport.use(
new Strategy(
{
passReqToCallback: true,
clientID: config.CLIENT_ID,
clientSecret: config.CLIENT_SECRET,
callbackURL: config.AUTH_CALLBACK,
@@ -174,9 +191,22 @@ export function initSession() {
export const router = express.Router();
router.get("/account-recovery", (req, res) => {
res.set("Cache-Control", "no-store");
res.json({ required: !!req.session.githubRecovery && req.session.githubRecovery.expires > Date.now() });
});
router.get(
"/login",
(req, res, next) => config.GITHUB_OAUTH_ENABLED ? next() : res.status(503).json({ error: "github_oauth_disabled" }),
(req, res, next) => {
const recovery = req.query.recover === "1" ? req.session.githubRecovery : undefined;
if (req.query.recover === "1" && (!recovery || recovery.expires < Date.now())) return res.redirect("/signin");
const current = (req.user as { user?: IUserDocument } | undefined)?.user;
req.session.githubOAuthFlow = recovery || { ownerId: current?.id, githubId: current?.externalIDs?.github,
returnTo: safeAuthReturnTo(req.query.returnTo, current ? "/connections" : "/dashboard"), expires: Date.now() + 10 * 60000 };
next();
},
passport.authenticate("github", { scope: ["repo"] }), // Note the scope here
function (req: express.Request, res: express.Response) {
res.redirect("/");
@@ -187,6 +217,9 @@ router.get(
"/auth",
(req, res, next) => {
if (!config.GITHUB_OAUTH_ENABLED) return res.status(503).json({ error: "github_oauth_disabled" });
const flow = req.session.githubOAuthFlow;
delete req.session.githubOAuthFlow;
req.githubOAuthContext = flow;
const existingId = (req.user as { user?: { id?: string } } | undefined)?.user?.id;
passport.authenticate("github", (error: Error | null, identity: Express.User | false) => {
if (error) return next(error);
@@ -195,7 +228,11 @@ router.get(
if (existingId && id !== existingId) return res.status(409).json({ error: "github_identity_mismatch" });
req.login(identity, loginError => {
if (loginError) return next(loginError);
res.redirect(existingId ? "/connections" : "/dashboard");
if (flow?.recovery) {
delete req.session.githubRecovery;
return res.redirect("/github/app/login?returnTo=" + encodeURIComponent(flow.returnTo));
}
res.redirect(flow?.returnTo || (existingId ? "/connections" : "/dashboard"));
});
})(req, res, next);
}
+11 -4
View File
@@ -11,14 +11,14 @@ import { getCredentialToken } from "../../core/credentials";
import { APP_PROVIDER, appError, appUserToken, AppInstallation, clearAppTokenCache, exchangeAppToken,
githubRequest, GitHubRepositoryInfo, reconcileAppGrant, reconcileInstallation, installationURL, saveAppGrant, selectRepositoryAccess, userInstallations } from "../../core/github-app";
import { getUser, handleError } from "./route-utils";
import { isDisabledAccount } from "./auth-utils";
import { isDisabledAccount, safeAuthReturnTo } from "./auth-utils";
type Flow = { state: string; expires: number; ownerId?: string; returnTo: string; repository?: string; install?: boolean };
declare module "express-session" {
interface SessionData { githubAppFlow?: Flow; githubInstallFlow?: Flow; githubConnectionCSRF?: string; }
}
export function safeReturnTo(value: unknown): string {
return typeof value === "string" && /^\/(?:(?:anonymize|pull-request-anonymize|gist-anonymize)(?:\/[\w-]+)?|connections|dashboard)(?:\?[^\\\r\n]*)?$/.test(value) ? value : "/connections";
return safeAuthReturnTo(value, "/connections");
}
export function consumeFlow(flow: Flow | undefined, state: unknown): Flow {
if (!flow || typeof state !== "string" || flow.state !== state || flow.expires < Date.now()) throw appError("invalid_auth_state", 400);
@@ -39,7 +39,7 @@ router.use((_req, res, next) => { res.set("Cache-Control", "no-store"); next();
router.get("/app/login", enabled, async (req, res) => {
try {
const ownerId = req.isAuthenticated() ? (await getUser(req)).id : undefined;
const flow = newFlow(ownerId, req.query.returnTo);
const flow = newFlow(ownerId, req.query.returnTo || (ownerId ? "/connections" : "/dashboard"));
flow.install = req.query.install === "1";
if (typeof req.query.repository === "string" && /^[\w.-]+\/[\w.-]+$/.test(req.query.repository)) flow.repository = req.query.repository;
req.session.githubAppFlow = flow;
@@ -73,7 +73,14 @@ router.get("/app/callback", enabled, async (req, res) => {
}
if (!user) {
// A matching login name alone is not proof of account ownership.
if (await UserModel.exists({ username: profile.login })) throw appError("github_account_link_required", 409);
const existing = await UserModel.findOne({ username: profile.login });
if (existing) {
if (existing.externalIDs?.github || isDisabledAccount(existing.status)) throw appError("github_identity_mismatch", 409);
req.session.githubRecovery = { ownerId: existing.id, githubId: String(profile.id), returnTo: flow.returnTo,
expires: Date.now() + 10 * 60000, recovery: true };
await saveSession(req);
return res.redirect("/signin?recover=1");
}
user = new UserModel({ username: profile.login, externalIDs: { github: String(profile.id) }, photo: profile.avatar_url, emails: [] });
await user.save();
}
+1 -1
View File
@@ -192,7 +192,7 @@ describeMongo("credential access (MongoDB)", function () {
it("OAuth login writes a credential and returns a token-free session user", async () => {
const passport = require("passport");
require("../src/server/routes/connection");
const result = await new Promise((resolve, reject) => passport._strategy("github")._verify("oauth-secret", "refresh-secret", {
const result = await new Promise((resolve, reject) => passport._strategy("github")._verify({ githubOAuthContext: { expires: Date.now() + 60000 } }, "oauth-secret", "refresh-secret", {
id: "external-test", username: owner.username, emails: [], photos: [],
}, (error, user) => error ? reject(error) : resolve(user)));
expect(JSON.stringify(result)).not.to.include("oauth-secret");
+1 -1
View File
@@ -197,7 +197,7 @@ describe("frontend production regressions", function () {
it("sends signed-out visitors to sign in instead of the dashboard", function () {
const manage = f => f.key === "manage";
const out = landing(null);
expect(out.scope.featureHref(out.scope.features.find(manage))).to.equal("/github/login");
expect(out.scope.featureHref(out.scope.features.find(manage))).to.equal("/signin");
expect(out.scope.featureTarget(out.scope.features.find(manage))).to.equal("_self");
const signedIn = landing({ username: "jane" });
expect(signedIn.scope.featureHref(signedIn.scope.features.find(manage))).to.equal("/dashboard");
+63 -3
View File
@@ -1,4 +1,5 @@
const { expect } = require("chai");
const { URL } = require("node:url");
const { createHmac, generateKeyPairSync, createVerify } = require("crypto");
const process = require("process");
const { setTimeout } = require("timers");
@@ -96,7 +97,7 @@ describe("GitHub App protocol boundaries", () => {
const describeMongo = process.env.TEST_MONGODB_URI ? describe : describe.skip;
describeMongo("GitHub App credential and repository integration", function () {
this.timeout(15000);
let owner, previousConfig, previousFetch, calls, server, base, session;
let owner, previousConfig, previousFetch, calls, server, base, session, authenticated;
const data = (suffix = "1", expires = 3600) => ({ access_token: "ghu_access" + suffix, refresh_token: "ghr_refresh" + suffix,
expires_in: expires, refresh_token_expires_in: 100000 });
before(async () => {
@@ -111,13 +112,15 @@ describeMongo("GitHub App credential and repository integration", function () {
api.use(express.json());
api.use((req, _res, next) => {
req.session = session;
req.user = { user: owner };
req.isAuthenticated = () => true;
req.session.regenerate = done => done();
req.user = authenticated ? { user: owner } : undefined;
req.isAuthenticated = () => authenticated;
req.login = (identity, done) => { req.user = identity; done(); };
req.logout = done => done();
next();
});
api.use("/github", githubAppRouter);
api.use("/github", require("../src/server/routes/connection").router);
server = await new Promise(resolve => { const listening = api.listen(0, "127.0.0.1", () => resolve(listening)); });
base = `http://127.0.0.1:${server.address().port}`;
});
@@ -134,6 +137,7 @@ describeMongo("GitHub App credential and repository integration", function () {
calls = [];
await Credentials.deleteMany({}); await Users.deleteMany({}); await Installations.deleteMany({});
app.clearAppTokenCache();
authenticated = true;
owner = await Users.create({ username: "owner", externalIDs: { github: "10" } });
});
afterEach(() => { globalThis.fetch = previousFetch; });
@@ -249,6 +253,62 @@ describeMongo("GitHub App credential and repository integration", function () {
expect(replay.status).to.equal(400);
expect(calls).to.have.length(2);
});
it("signs an existing user in through the App without changing legacy access", async () => {
authenticated = false;
await setCredential(owner.id, "legacy-secret");
session.githubAppFlow = { state: "state", expires: Date.now() + 60000, returnTo: "/dashboard" };
mock(url => url.includes("/login/oauth/access_token") ? data() : { id: 10, login: "owner-renamed" });
const result = await request("/github/app/callback?state=state&code=code");
expect(result.location).to.equal("/dashboard");
expect(await Users.countDocuments()).to.equal(1);
expect(await getCredentialToken(owner.id)).to.equal("legacy-secret");
expect(await app.appUserToken(owner.id)).to.equal("ghu_access1");
});
it("requires legacy verification for an older account before linking its GitHub ID", async () => {
authenticated = false;
await Users.updateOne({ _id: owner._id }, { $unset: { externalIDs: 1 } });
session.githubAppFlow = { state: "state", expires: Date.now() + 60000, returnTo: "/gist-anonymize" };
mock(url => url.includes("/login/oauth/access_token") ? data() : { id: 10, login: "owner" });
const result = await request("/github/app/callback?state=state&code=code");
expect(result.location).to.equal("/signin?recover=1");
expect(session.githubRecovery.ownerId).to.equal(owner.id);
expect(session.githubRecovery.githubId).to.equal("10");
expect(await Credentials.countDocuments()).to.equal(0);
expect((await Users.findById(owner.id)).externalIDs?.github).to.equal(undefined);
const { verify } = require("../src/server/routes/connection");
const identity = await new Promise((resolve, reject) => verify({ githubOAuthContext: session.githubRecovery },
"verified-legacy", "", { id: "10", username: "owner" }, (error, user) => error ? reject(error) : resolve(user)));
expect(identity.user.id).to.equal(owner.id);
expect((await Users.findById(owner.id)).externalIDs.github).to.equal("10");
expect(await getCredentialToken(owner.id)).to.equal("verified-legacy");
});
it("returns OAuth connections to the gist form and rejects callback replay", async () => {
const strategy = require("passport")._strategy("github");
const exchange = strategy._oauth2.getOAuthAccessToken;
const profile = strategy.userProfile;
strategy._oauth2.getOAuthAccessToken = (_code, _params, done) => done(null, "gist-oauth", "", {});
strategy.userProfile = (_token, done) => done(null, { id: "10", username: "owner" });
try {
const start = await request("/github/login?returnTo=%2Fgist-anonymize%2Fsaved");
const state = new URL(start.location).searchParams.get("state");
const result = await request("/github/auth?code=test&state=" + state);
expect(result.location).to.equal("/gist-anonymize/saved");
expect(await getCredentialToken(owner.id)).to.equal("gist-oauth");
expect((await request("/github/auth?code=test&state=" + state)).location).to.equal("/signin");
await request("/github/login?returnTo=https%3A%2F%2Fevil.test");
expect(session.githubOAuthFlow.returnTo).to.equal("/connections");
} finally {
strategy._oauth2.getOAuthAccessToken = exchange;
strategy.userProfile = profile;
}
});
it("rejects a different account during OAuth recovery before saving credentials", async () => {
const { verify } = require("../src/server/routes/connection");
await rejects(new Promise((resolve, reject) => verify({ githubOAuthContext: { ownerId: owner.id, githubId: "10", expires: Date.now() + 60000 } },
"wrong-token", "", { id: "99", username: "owner" }, (error, user) => error ? reject(error) : resolve(user))), "github_identity_mismatch");
expect(await Credentials.countDocuments()).to.equal(0);
expect((await Users.findById(owner.id)).externalIDs.github).to.equal("10");
});
it("rejects linking a different GitHub identity", async () => {
session.githubAppFlow = { state: "state", ownerId: owner.id, expires: Date.now() + 60000, returnTo: "/connections" };
mock(url => url.includes("/login/oauth/access_token") ? data() : { id: 99, login: "other" });
+1 -1
View File
@@ -146,7 +146,7 @@ describe("production regressions", function () {
let calls = 0;
stub(UserModel, "findOne", async () => ++calls === 1 ? null : { externalIDs: { github: "old-id" }, isAdmin: true });
stub(UserModel, "updateOne", () => { throw new Error("must not overwrite identity"); });
const error = await new Promise(resolve => passport._strategy("github")._verify("token", "", { id: "new-id", username: "recycled" }, resolve));
const error = await new Promise(resolve => passport._strategy("github")._verify({ githubOAuthContext: { expires: Date.now() + 60000 } }, "token", "", { id: "new-id", username: "recycled" }, resolve));
expect(error.message).to.equal("not_connected");
});
+35 -1
View File
@@ -89,10 +89,44 @@ describe("Vue 3 UI", function () {
expect(ui.errors).to.deep.equal([]);
});
it("offers App and OAuth sign-in and direct repository access links", async function () {
it("offers one App sign-in for new and existing accounts", async function () {
ui = await browser("/signin", { "/api/options": { GITHUB_APP_ENABLED: true, GITHUB_OAUTH_ENABLED: true } });
expect(ui.window.document.querySelector('a[href="/github/app/login"]')).not.to.equal(null);
expect(ui.window.document.querySelector('a[href="/github/login"]')).to.equal(null);
expect(ui.errors).to.deep.equal([]);
});
it("shows previous-connection verification only for a pending recovery", async function () {
ui = await browser("/signin?recover=1", {
"/api/options": { GITHUB_APP_ENABLED: true, GITHUB_OAUTH_ENABLED: true },
"/github/account-recovery": { required: true },
});
expect(ui.window.document.querySelector('a[href="/github/login?recover=1"]')).not.to.equal(null);
expect(ui.window.document.querySelector('a[href="/github/app/login"]')).to.equal(null);
expect(ui.errors).to.deep.equal([]);
});
it("falls back to one OAuth sign-in when the App is disabled", async function () {
ui = await browser("/signin", { "/api/options": { GITHUB_APP_ENABLED: false, GITHUB_OAUTH_ENABLED: true } });
expect(ui.window.document.querySelector('a[href="/github/login"]')).not.to.equal(null);
expect(ui.window.document.querySelector('a[href="/github/app/login"]')).to.equal(null);
});
it("offers OAuth for an App-only gist and saves the draft before connecting", async function () {
ui = await browser("/gist-anonymize", {
"/api/user": { username: "owner" },
"/github/connections": { appEnabled: true, appConnected: true, oauthEnabled: true, oauthConnected: false },
});
const input = await ui.input("#sourceUrl", "https://gist.github.com/311fc9");
input.dispatchEvent(new ui.window.Event("blur"));
await delay(60);
const button = [...ui.window.document.querySelectorAll("button")].find(node => node.textContent.includes("Connect GitHub OAuth to access gists"));
expect(button).not.to.equal(undefined);
expect(ui.requests.some(r => r.url.pathname === "/api/gist/source/311fc9")).to.equal(false);
button.click();
const saved = JSON.parse(ui.window.sessionStorage.getItem("github-access-draft"));
expect(saved.path).to.equal("/gist-anonymize");
expect(saved.draft.sourceUrl).to.equal("https://gist.github.com/311fc9");
expect(ui.errors).to.deep.equal([]);
});