Files
anonymous_github/public/partials/pageView.htm
T
Thomas DurieuxandClaude Opus 5.5 2600bfa2f6 fix(ui): go-to-line popover, selection overlap, copy icon, date and checkout layout
- Go to line: themed popover under the file toolbar (line or line:column,
  range check) instead of Ace's unthemed prompt
- Code selection: no per-box outline, so multi-line selections no longer
  overlap at the joins; single-line matches keep an inset outline
- My work: copy-link is an icon button (URL in the tooltip); phone cards
  drop the URL row and truncate long names
- Date fields: drop WebKit's native button styling so they fit their column
- Submit bar: the checkout note gets its own line under the button

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 10:33:06 +00:00

153 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<section class="dir-card" v-if="sidebarHidden &amp;&amp; type == 'html' &amp;&amp; content != null &amp;&amp; paths?.length === 1 &amp;&amp; /^readme/i.test(paths[0] || '') &amp;&amp; files?.length" aria-label="Repository files">
<header class="dir-card-head">
<span class="dir-card-title">Files</span>
<span class="dir-card-sub">Snapshot of the repository at review time</span>
</header>
<ul class="dir-list" :class="{ 'is-expanded': dirExpanded }">
<li v-for="entry in rootEntries()" :key="entry.name">
<button type="button" class="dir-row" v-if="entry.size == null" @click="revealFolder(entry.name)">
<svg class="dir-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M1.5 3.5h4l1.5 1.5h7.5v8h-13z"/></svg>
<span class="dir-name">{{ entry.name }}</span>
<span class="dir-meta">Folder</span>
</button>
<a class="dir-row" v-else :class="{ 'is-current': entry.name === paths[0] }" :href="safeUrl('/r/' + repoId + '/' + encodeURIComponent(entry.name))">
<svg class="dir-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 1.5h6l3 3v10h-9z"/></svg>
<span class="dir-name">{{ entry.name }}</span>
<span class="dir-meta">{{ formatSize(entry.size) }}</span>
</a>
</li>
</ul>
<button type="button" class="dir-more" v-if="!dirExpanded &amp;&amp; files.length &gt; 8" @click="dirExpanded = true">Show all {{ files.length }} items</button>
</section>
<section class="file-card" :class="{ 'is-code': type != 'html', 'no-wrap': !wrapLines }" v-if="(type == 'text' || type == 'code' || type == 'html' || (type == 'html-doc' &amp;&amp; showSource)) &amp;&amp; content != null">
<header class="file-card-head">
<div class="file-card-path">
<button type="button" class="btn btn-sm btn-icon-only btn-ghost card-files-btn" :class="{ 'is-needed': sidebarHidden }" aria-label="Show file tree" title="Show file tree" @click="sidebarHidden ? (sidebarHidden = false) : (sidebarCollapsed = false)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-sidebar"></use></svg></button>
<ol class="breadcrumb paths" aria-label="Path">
<li class="breadcrumb-item breadcrumb-root"><a :href="safeUrl('/r/' + repoId + '/')" :title="repoId" v-text="/^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(repoId || '') ? 'repository' : repoId"></a></li>
<li class="breadcrumb-item" v-for="(p, index) in paths" :aria-current="index === paths.length - 1 ? 'page' : null"><template v-for="(seg, si) in redactParts(p)" :key="si"><span v-if="seg.n" class="redact-slot" role="img" :aria-label="'Redacted term ' + seg.n"><i>{{ seg.n }}</i></span><template v-else>{{ seg.t }}</template></template></li>
</ol>
<span class="mobile-sub"><span>{{ type == 'html' ? 'Markdown' : (languageName || 'Text') }}</span><span v-if="type != 'html' &amp;&amp; typeof content === 'string'">{{ fmt?.number(content.split('\n').length) }} lines</span><span v-if="type == 'html' && fileSize(filePath) != null">{{ formatSize(fileSize(filePath)) }}</span><b v-if="redactionCount()">{{ redactionCount() }} redacted</b></span>
<button type="button" class="btn btn-sm btn-icon-only btn-ghost copy-path-btn" v-show="filePath" :aria-label="pathCopied ? 'Path copied' : 'Copy file path'" :title="pathCopied ? 'Path copied' : 'Copy file path'" @click="copyPath()"><svg class="ui-icon" aria-hidden="true"><use :href="pathCopied ? '#ui-icon-check' : '#ui-icon-link'"></use></svg></button>
</div>
<span class="file-card-meta" v-if="type != 'html' &amp;&amp; typeof content === 'string'"><template v-if="languageName">{{ languageName }}<span class="sep">·</span></template>{{ fmt?.number(content.split('\n').length) }} lines<span class="meta-extra"><span class="sep">·</span>{{ content.length &lt; 1024 ? content.length + ' B' : (content.length / 1024).toFixed(1) + ' KB' }}</span><template v-if="redactionCount()"><span class="sep">·</span><span class="meta-redact">{{ redactionCount() }} redacted</span></template></span>
<span class="file-card-meta" v-if="type == 'html'">Markdown<template v-if="fileSize(filePath) != null"><span class="sep">·</span>{{ formatSize(fileSize(filePath)) }}</template><template v-if="redactionCount()"><span class="sep">·</span><span class="meta-redact">{{ redactionCount() }} redacted</span></template></span>
<div class="file-card-actions">
<div class="symbols" v-if="type != 'html' &amp;&amp; symbols().length &gt; 1">
<button type="button" class="btn btn-sm btn-ghost btn-symbols" :class="{ 'is-on': symbolsOpen }" :aria-expanded="symbolsOpen ? 'true' : 'false'" aria-haspopup="true" title="Symbols in this file" @click="symbolsOpen = !symbolsOpen"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg><span class="btn-find-label">Symbols</span></button>
<div class="symbols-menu" v-if="symbolsOpen" role="menu">
<div class="symbols-head">{{ symbols().length }} {{ symbols().length === 1 ? 'symbol' : 'symbols' }}</div>
<button type="button" class="symbols-item" role="menuitem" v-for="sym in symbols()" :key="sym.line + sym.name" @click="goToSymbol(sym.line)"><span class="symbols-kind" :class="'is-' + sym.kind">{{ sym.kind === 'class' ? 'C' : sym.kind === 'function' ? 'ƒ' : 'v' }}</span><span class="symbols-name">{{ sym.name }}</span><span class="symbols-line">{{ sym.line }}</span></button>
</div>
</div>
<button type="button" class="btn btn-sm btn-ghost btn-icon-only btn-find btn-md-find" v-if="type == 'html'" :class="{ 'is-on': mdFindOpen }" aria-label="Find on this page" :title="'Find on this page (' + (isMac ? '⌘' : 'Ctrl+') + 'F)'" @click="toggleMdFind()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-search"></use></svg></button>
<button type="button" class="btn btn-sm btn-ghost btn-icon-only btn-find" v-if="type != 'html'" aria-label="Find in file" :title="'Find in file (' + (isMac ? '⌘' : 'Ctrl+') + 'F)'" @click="findInFile()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-search"></use></svg></button>
<button type="button" class="btn btn-sm btn-ghost btn-icon-only btn-goto" v-if="type != 'html'" aria-label="Go to line" :title="'Go to line (' + (isMac ? '⌘' : 'Ctrl+') + 'G)'" @click="gotoLine()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-goto"></use></svg></button>
<button type="button" class="btn btn-sm btn-icon-only btn-ghost btn-toggle" v-if="type != 'html'" :class="{ 'is-on': wrapLines }" :aria-pressed="wrapLines ? 'true' : 'false'" aria-label="Wrap long lines" :title="'Wrap long lines (Alt+Z)'" @click="toggleWrap()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-wrap"></use></svg></button>
<div class="tb-file" role="group" aria-label="File actions">
<a target="_self" class="tb-seg-btn" :title="type == 'html' ? 'Open the Markdown source' : 'Open the raw file'" :href="safeUrl((url))">Raw</a>
<button type="button" class="tb-seg-btn tb-seg-icon" v-if="typeof content === 'string'" :aria-label="contentCopied ? 'Copied' : 'Copy file contents'" :title="contentCopied ? 'Copied' : 'Copy file contents'" @click="copyContent()"><svg class="ui-icon" aria-hidden="true"><use :href="contentCopied ? '#ui-icon-check' : '#project-icon-copy'"></use></svg></button>
<a target="_self" class="tb-seg-btn tb-seg-icon" aria-label="Download file" title="Download file" :href="safeUrl((url) + &quot;&amp;download=true&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-download"></use></svg></a>
</div>
<button type="button" class="btn btn-sm btn-icon-only btn-ghost tb-copy-m" v-if="typeof content === 'string'" :aria-label="contentCopied ? 'Copied' : 'Copy file contents'" @click="copyContent()"><svg class="ui-icon" aria-hidden="true"><use :href="contentCopied ? '#ui-icon-check' : '#project-icon-copy'"></use></svg></button>
<div class="tb-toc-m" v-if="type == 'html' &amp;&amp; readmeToc().length &gt;= 3">
<button type="button" class="btn btn-sm btn-icon-only btn-ghost tb-toc-btn" :class="{ 'is-on': tocOpen }" aria-label="On this page" title="On this page" aria-haspopup="true" :aria-expanded="tocOpen ? 'true' : 'false'" @click.stop="tocOpen = !tocOpen; moreOpen = false"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-outline"></use></svg></button>
<div class="tb-menu tb-toc-menu" v-if="tocOpen" role="menu" @click="tocOpen = false">
<div class="tb-menu-label">On this page</div>
<button type="button" role="menuitem" :class="['tb-menu-toc', 'lvl-2', { 'is-active': !activeHeading }]" @click="scrollToHeading(null)">Introduction</button>
<button type="button" role="menuitem" v-for="h in readmeToc()" :key="'m-' + h.id" :class="['tb-menu-toc', 'lvl-' + h.level, { 'is-active': activeHeading === h.id }]" @click="scrollToHeading(h.id)">{{ h.text }}</button>
</div>
</div>
<div class="tb-more">
<button type="button" class="btn btn-sm btn-icon-only btn-ghost tb-more-btn" :class="{ 'is-on': moreOpen }" aria-label="More actions" aria-haspopup="true" :aria-expanded="moreOpen ? 'true' : 'false'" @click.stop="tocOpen = false; moreOpen = !moreOpen"><svg viewBox="0 0 24 24" class="ui-icon" aria-hidden="true" style="fill:currentColor;stroke:none"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg></button>
<div class="tb-menu" v-if="moreOpen" role="menu" @click="moreOpen = false">
<div class="tb-menu-note" v-if="options?.publishedAt || options?.sourceCommitDate || options?.anonymizedAt || options?.lastUpdateDate"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>Snapshot · {{ fmt?.date(options?.publishedAt || options?.sourceCommitDate || options?.anonymizedAt || options?.lastUpdateDate) }}</div>
<div class="tb-menu-note tb-menu-meta" v-if="typeof content === 'string'">{{ type == 'html' ? 'Markdown' : (languageName || 'Text') }}<template v-if="type != 'html'"> · {{ fmt?.number(content.split('\n').length) }} lines</template> · {{ type == 'html' &amp;&amp; fileSize(filePath) != null ? formatSize(fileSize(filePath)) : (content.length &lt; 1024 ? content.length + ' B' : (content.length / 1024).toFixed(1) + ' KB') }}</div>
<div class="tb-menu-sep" v-if="options?.publishedAt || options?.sourceCommitDate || options?.anonymizedAt || options?.lastUpdateDate"></div>
<button type="button" role="menuitem" v-if="type == 'html'" @click="toggleMdFind()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-search"></use></svg>Find in file</button>
<div class="tb-menu-sep" v-if="type == 'html'"></div>
<button type="button" role="menuitem" v-if="type != 'html'" @click="findInFile()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-search"></use></svg>Find in file</button>
<button type="button" role="menuitem" v-if="type != 'html'" @click="gotoLine()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-goto"></use></svg>Go to line</button>
<button type="button" role="menuitemcheckbox" v-if="type != 'html'" :aria-checked="wrapLines ? 'true' : 'false'" @click="toggleWrap()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-wrap"></use></svg>Wrap lines<span class="tb-menu-check" v-if="wrapLines">✓</span></button>
<div class="tb-menu-sep" v-if="type != 'html'"></div>
<a role="menuitem" target="_self" v-if="type == 'html'" :href="safeUrl((url))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg>View raw</a>
<a role="menuitem" target="_self" v-if="type != 'html'" :href="safeUrl((url))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg>View raw</a>
<a role="menuitem" target="_self" :href="safeUrl((url) + &quot;&amp;download=true&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-download"></use></svg>Download file</a>
<a role="menuitem" target="_self" v-if="options?.hasWebsite" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-globe"></use></svg>Open website</a>
<a role="menuitem" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/anonymize/&quot; + (repoId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg>Edit anonymization</a>
<a role="menuitem" target="_self" v-if="options?.download" :href="safeUrl(&quot;/api/repo/&quot; + (repoId) + &quot;/zip&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-archive"></use></svg>Download ZIP</a>
<button type="button" role="menuitem" @click="copyPath()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-link"></use></svg>Copy file path</button>
<div class="tb-menu-sep"></div>
<a role="menuitem" href="/faq"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-help"></use></svg>FAQ</a>
<a role="menuitem" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg>Report a bug</a>
</div>
</div>
</div>
<form class="goto-pop" v-if="gotoOpen &amp;&amp; type != 'html'" role="dialog" aria-label="Go to line" @submit.prevent="submitGoto()">
<label class="goto-label" for="goto-input">Go to line</label>
<input id="goto-input" class="goto-input" type="text" inputmode="numeric" autocomplete="off" spellcheck="false" :placeholder="'1–' + fmt?.number(typeof content === 'string' ? content.split('\n').length : 1)" :aria-invalid="gotoError ? 'true' : 'false'" v-model="gotoValue" @keydown.esc.prevent.stop="closeGoto()">
<span class="goto-hint" :class="{ 'is-error': gotoError }">{{ gotoError || 'Line, or line:column' }}</span>
</form>
</header>
<div class="file-card-body" v-if="type != 'html'" v-code-editor="{ content: content, options: aceOption }"></div>
<div class="md-find-bar" v-if="type == 'html' &amp;&amp; mdFindOpen" role="search">
<input type="search" class="md-find-input" placeholder="Find on this page" aria-label="Find on this page" :value="mdFindQuery" @input="mdFindQuery = $event.target.value; runMdFind()" @keydown.enter.prevent="$event.shiftKey ? stepMdFind(-1) : stepMdFind(1)" @keydown.esc.prevent="toggleMdFind(false)">
<span class="md-find-nav"><button type="button" aria-label="Previous match" @click="stepMdFind(-1)" :disabled="!mdFindTotal"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg></button><button type="button" aria-label="Next match" @click="stepMdFind(1)" :disabled="!mdFindTotal"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button></span>
<span class="md-find-count" aria-live="polite">{{ mdFindQuery ? (mdFindTotal ? (mdFindIndex + 1) + ' of ' + mdFindTotal : 'No results') : '' }}</span>
<button type="button" class="md-find-opt" :class="{ 'is-on': mdFindCase }" :aria-pressed="mdFindCase ? 'true' : 'false'" title="Match case" aria-label="Match case" @click="mdFindCase = !mdFindCase; runMdFind()">Aa</button>
<button type="button" class="md-find-close" aria-label="Close find" @click="toggleMdFind(false)">&times;</button>
</div>
<div class="reading-layout" v-if="type == 'html'">
<div class="file-content markdown-body" v-html="renderReadme(sanitize(content))"></div>
<aside class="readme-toc" v-if="readmeToc().length &gt;= 3" aria-label="On this page">
<div class="readme-toc-head">On this page</div>
<nav class="toc-rail"><a href="#" class="lvl-2 toc-top" :class="{ 'is-active': !activeHeading }" @click.prevent="scrollToHeading(null)">Introduction</a><a v-for="(h, i) in readmeToc()" :key="h.id" :href="'#' + h.id" :class="['lvl-' + h.level, { 'is-active': activeHeading === h.id }]" @click.prevent="scrollToHeading(h.id)">{{ h.text }}</a></nav>
</aside>
</div>
</section>
<html-doc v-if="type == &#x27;html-doc&#x27; &amp;&amp; !showSource &amp;&amp; content != null" :content="content" :base-url="(fileBaseUrl)" :allow-scripts="allowScripts"></html-doc>
<img class="image-content" v-if="type == &#x27;image&#x27;" :src="safeUrl((url))">
<iframe class="h-100 overflow-auto w-100 b-0" v-if="type == &#x27;media&#x27;" :src="safeUrl((url))"></iframe>
<pdfviewer v-if="type == &#x27;pdf&#x27;" :src="safeUrl((url))"></pdfviewer>
<div v-if="type == &#x27;audio&#x27;"><audio controls="controls"><source :src="safeUrl((url))"></audio></div>
<div v-if="type == &#x27;IPython&#x27;"><notebook :file="url" :content="content"></notebook></div>
<div class="file-error container d-flex h-100" v-if="type == &#x27;rate_limited&#x27;">
<div class="paper-ratelimit-card m-auto" style="max-width:520px;">
<div class="paper-ratelimit-head">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-hourglass"></use></svg>
<div>
<div class="paper-error-eyebrow">Temporarily paused</div>
<div class="paper-error-title">GitHub API rate limit reached</div>
</div>
</div>
<p class="paper-error-msg">This repository will be available in <strong>{{ rateLimitCountdown }}</strong>. The page will reload automatically.</p>
</div>
</div>
<div class="file-error container d-flex h-100" v-if="type == 'error'">
<section class="viewer-error paper-error-card file-error-card m-auto" role="alert" aria-labelledby="viewer-error-title">
<h1 id="viewer-error-title" class="paper-empty-title">{{ fmt.translate(isSourceAccessError() ? 'VIEWER_ERRORS.access_title' : 'VIEWER_ERRORS.title') }}</h1>
<p>{{ fmt.translate('ERRORS.' + content) }}</p>
<template v-if="isSourceAccessError()">
<p>{{ fmt.translate('VIEWER_ERRORS.reader_help') }}</p>
<p>{{ fmt.translate('VIEWER_ERRORS.owner_help') }}</p>
</template>
<div class="paper-error-actions">
<a v-if="!isSourceAccessError()" href="/" class="btn btn-sm">Back to home</a>
<a v-if="!isSourceAccessError()" href="/faq" class="btn btn-sm">Read the FAQ</a>
<button type="button" class="btn btn-primary" @click="retryContent()">{{ fmt.translate('VIEWER_ERRORS.retry') }}</button>
<a v-if="isSourceAccessError()" class="btn btn-outline-ink" href="/dashboard">{{ fmt.translate('VIEWER_ERRORS.dashboard') }}</a>
</div>
</section>
</div>
<section class="viewer-loading" v-if="type == 'loading' &amp;&amp; !error" aria-busy="true">
<div class="viewer-loading-status" role="status" aria-live="polite">
<strong>{{ fmt.translate(fileReceivedBytes ? 'VIEWER_LOADING.receiving' : 'VIEWER_LOADING.loading') }}</strong>
<span class="viewer-loading-name sr-only">{{ filePath }}</span>
<p v-if="fileLoadingSlow">{{ fmt.translate('VIEWER_LOADING.slow') }}</p>
</div>
<div class="viewer-loading-lines" aria-hidden="true"><span v-for="line in 10" :key="line"></span></div>
</section>
<div class="file-error container d-flex h-100" v-if="type == &#x27;empty&#x27;"><h1 class="paper-empty-title m-auto">Empty <em>repository</em>.</h1></div>
<div class="file-error container d-flex h-100" v-if="content == null &amp;&amp; !['empty', 'loading', 'error', 'rate_limited'].includes(type)"><h1 class="paper-empty-title m-auto">Empty <em>file</em>.</h1></div>
<div class="file-error container d-flex h-100" v-if="type == &#x27;binary&#x27;"><h1 class="paper-empty-title m-auto">Unsupported <em>binary file</em>. You can <a target="_blank" :href="safeUrl((url) + &quot;&amp;download=true&quot;)">download it</a>.</h1></div>