Files
anonymous_github/public/partials/dashboard.htm
T
Thomas DurieuxandClaude Opus 5.5 c044b83541 feat(ui): one consistent design across author, reviewer and admin screens
The UI was uneven: the home page looked finished, but app pages used three
different display typefaces, the anonymize flow changed frame between steps,
and the dashboard, explorer and admin screens each had their own controls,
status colours and edge-case handling.

Make every screen follow one system: shared page header, grid, buttons,
filters and status colours; a repository picker and step trail on anonymize;
a "What reviewers will see" preview of the actual reviewer page; consistent
file headers and viewers for every file type; truthful admin charts and
tables. Fold the accumulated override blocks back into style.css (22%
smaller, no visual change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 02:22:45 +00:00

279 lines
31 KiB
HTML

<div class="container page dashboard-page paper-page">
<div class="row">
<div class="w-100">
<div class="dashboard-heading page-head page-head-main">
<div>
<div class="dashboard-title-line"><h1 class="paper-page-title">My work</h1><span class="dashboard-count-head" v-if="!loading &amp;&amp; isPlainView()" aria-hidden="true">{{ fmt?.number(dashboardTotals?.filtered ?? filteredItems?.length) }} project<span v-if="(dashboardTotals?.filtered ?? filteredItems?.length) !== 1">s</span></span></div>
<p class="paper-page-lede">Manage your anonymous links.</p>
</div>
<div class="dashboard-heading-actions">
<details class="dashboard-quota" :open="quotaExpanded" @toggle="quotaExpanded = $event.target.open" @keydown.esc.prevent.stop="closeQuota()">
<summary aria-controls="dashboard-usage" :aria-expanded="quotaExpanded" :class="quota ? 'quota-level-' + quota.repository.level : ''" :style="quota && !quota.repository.unlimited ? { '--quota-pct': quota.repository.percent + '%' } : null">
<svg class="quota-summary-ring" viewBox="0 0 24 24" aria-hidden="true" :class="quota ? 'quota-' + quota.repository.level : ''">
<circle class="quota-ring-track" cx="12" cy="12" r="9"></circle>
<circle class="quota-ring-fill" cx="12" cy="12" r="9" pathLength="100" v-if="quota &amp;&amp; !quota.repository.unlimited" :stroke-dasharray="quota.repository.percent + ' 100'"></circle>
<path v-if="quota?.repository.unlimited" d="M7 12c0-4 4-4 5 0s5 4 5 0-4-4-5 0-5 4-5 0" class="quota-ring-unlimited"></path>
</svg>
<span class="quota-summary-label">Usage</span>
<span class="quota-summary-value" v-if="quota">{{ fmt.number(quota.repository.used) }}<span v-if="!quota.repository.unlimited"> / {{ fmt.number(quota.repository.total) }}</span> repos</span>
<svg class="project-type-svg quota-summary-chevron" aria-hidden="true"><use href="#project-icon-chevron"></use></svg>
</summary>
<div class="quota-panel" id="dashboard-usage">
<div class="quota-panel-heading"><h2>Account usage</h2><button type="button" class="project-close" aria-label="Close usage" @click="closeQuota()"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></div>
<quota-usage :quota="quota" :fmt="fmt" v-if="quota"></quota-usage>
<p class="quota-loading" role="status" v-else>Loading usage…</p>
</div>
</details>
<a href="/anonymize" class="btn btn-ink" aria-label="New anonymization">
<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-plus"></use></svg><span class="dashboard-new-label">New anonymization</span><span class="dashboard-new-mobile" aria-hidden="true">New</span>
</a>
</div>
</div>
<!-- Result count + active filter chips -->
<div class="dashboard-meta" v-if="!loading">
<div class="dashboard-view-tabs" role="group" aria-label="Project view" v-if="showViewTabs()">
<button type="button" :aria-pressed="statusView() === 'active'" @click="setStatusView('active')" title="Every project except removed links">Current</button>
<button type="button" v-if="attentionCount() &gt; 0 || statusView() === 'attention'" :aria-pressed="statusView() === 'attention'" @click="setStatusView('attention')" :aria-label="'Needs attention ' + attentionCount()"><span class="dashboard-attention-prefix">Needs </span><span>attention</span> <span class="attention-count" :class="{'has-attention': attentionCount() > 0}">{{ attentionCount() }}</span></button>
<button type="button" v-if="removedCount &gt; 0 || statusView() === 'removed' || (removedCount == null &amp;&amp; dashboardTotals?.total &gt; dashboardTotals?.filtered)" :aria-pressed="statusView() === 'removed'" @click="setStatusView('removed')" :aria-label="'Removed ' + (removedCount ?? '')" title="Links reviewers can no longer open">Removed <span class="attention-count" v-if="removedCount != null">{{ fmt?.number(removedCount) }}</span></button>
</div>
<span class="dashboard-count" :class="{'is-plain': isPlainView()}">
<span v-if="isPlainView()">{{ fmt?.number(dashboardTotals?.filtered ?? filteredItems?.length) }} project<span v-if="(dashboardTotals?.filtered ?? filteredItems?.length) !== 1">s</span></span>
<span v-else>{{ fmt?.number(dashboardTotals?.filtered ?? filteredItems?.length) }} shown</span>
</span>
</div>
<!-- Search + filters -->
<form class="w-100 dashboard-filter-row" aria-label="Filter anonymizations" accept-charset="UTF-8" v-form="viewState" @submit.prevent="submitForm($event, () =&gt; { $event.preventDefault() })">
<div class="search-wrap">
<input type="search" id="search" class="form-control" aria-label="Search anonymizations" placeholder="Search projects…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { search = value }, value: search, form: null, options: {} }">
</div>
<button class="btn dashboard-filters-toggle" type="button" :aria-expanded="filtersExpanded" aria-controls="dashboard-filter-controls" @click="filtersExpanded = !filtersExpanded">Filters<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-filter"></use></svg></button>
<div id="dashboard-filter-controls" class="dashboard-filter-controls" :class="{'expanded': filtersExpanded}">
<div class="btn-group" role="group" aria-label="Type">
<button type="button" class="btn" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;all&#x27;}" @click="typeFilter = &#x27;all&#x27;" :aria-pressed="(typeFilter === &#x27;all&#x27;)">All</button>
<button type="button" class="btn dashboard-type-filter" title="Repositories" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;repo&#x27;}" @click="typeFilter = &#x27;repo&#x27;" :aria-pressed="(typeFilter === &#x27;repo&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-repo"></use></svg><span class="type-filter-label">Repos</span></button>
<button type="button" class="btn dashboard-type-filter" title="Pull requests" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;pr&#x27;}" @click="typeFilter = &#x27;pr&#x27;" :aria-pressed="(typeFilter === &#x27;pr&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-pr"></use></svg><span class="type-filter-label">PRs</span></button>
<button type="button" class="btn dashboard-type-filter" title="Gists" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;gist&#x27;}" @click="typeFilter = &#x27;gist&#x27;" :aria-pressed="(typeFilter === &#x27;gist&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-gist"></use></svg><span class="type-filter-label">Gists</span></button>
</div>
<div id="dashboard-extra-filters" class="dashboard-extra-filters" :class="{'expanded': filtersExpanded}">
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="filter-btn-label">Sort</span>
<span class="filter-btn-value">{{ sortLabel() }}</span>
<svg class="project-type-svg sort-direction" aria-hidden="true" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
<span class="sr-only">{{ sortDesc() ? 'descending' : 'ascending' }}</span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Order by</h6>
<div class="form-check dropdown-item" v-for="(def, field, index) in sortFields">
<input class="form-check-input" type="radio" name="sort" @click="setSort(field, def.defaultDesc)" :checked="isSortedBy(field)" :id="&quot;sort-&quot; + (index)">
<label class="form-check-label" :for="&quot;sort-&quot; + (index)">{{ def?.label }}</label>
</div>
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item" @click="toggleSortDirection()">
{{ sortDesc() ? 'Switch to ascending' : 'Switch to descending' }}
</button>
</div>
</div>
<div class="dropdown dashboard-status-filter" :class="{'is-view-hidden': statusView() === 'removed'}" :inert="statusView() === 'removed' ? true : null">
<button class="btn dropdown-toggle" type="button" id="dropdownStatus" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="filter-btn-label">Status</span>
<span class="filter-btn-value">{{ statusChipKeys().length === 0 ? 'Any' : statusChipKeys().length === 1 ? 'Hiding ' + (statusKeyLabels[statusChipKeys()[0]] || '').toLowerCase() : statusChipKeys().length + ' hidden' }}</span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Show statuses</h6>
<button type="button" class="dropdown-item status-preset" :class="{active: statusChipKeys().length === 0}" @click="statusChipKeys().forEach(k => { filters.status[k] = true })">Any status</button>
<div class="dropdown-divider"></div>
<template v-for="(label, key, index) in statusKeyLabels" :key="key"><div class="form-check dropdown-item" v-if="key !== 'removed'">
<input class="form-check-input" type="checkbox" v-field="{ state: viewState, set: value =&gt; { filters.status[key] = value }, value: filters?.status[key], form: null, options: {} }" :id="&quot;status-&quot; + (key)">
<label class="form-check-label" :for="&quot;status-&quot; + (key)">{{ label }}</label>
</div></template>
</div>
</div>
</div>
</div>
</form>
<div class="dashboard-active-filters dashboard-active-filters-row" v-if="!loading &amp;&amp; showClearFilters()">
<template v-for="f in statusChipKeys()" :key="f"><button type="button" class="filter-chip" @click="filters.status[f] = true" :aria-label="'Show ' + statusKeyLabels[f] + ' again'" :title="'Show ' + statusKeyLabels[f] + ' again'">Hiding {{ (statusKeyLabels[f] || '').toLowerCase() }}<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></template>
<button type="button" class="filter-chip" v-if="typeFilter !== 'all'" @click="typeFilter = 'all'" aria-label="Show all project types">{{ typeFilter === 'repo' ? 'Repositories' : typeFilter === 'pr' ? 'Pull requests' : 'Gists' }}<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
<button type="button" class="btn-link-inline" v-if="showClearFilters()" @click="clearFilters()">Clear filters</button>
</div>
</div>
<!-- Table -->
<div class="paper-table paper-table-dashboard" role="table" aria-label="Anonymizations" :aria-busy="(loading)" :class="{'is-removed-view': statusView() === 'removed'}">
<div class="paper-table-head" role="row">
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;_label&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;_label&#x27;)}" @click="setSort(&#x27;_label&#x27;)">
Project <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;_label&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;status&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;status&#x27;)}" @click="setSort(&#x27;status&#x27;)">
Status <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;status&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" class="num" :aria-sort="(isSortedBy(&#x27;pageView&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;pageView&#x27;)}" @click="setSort(&#x27;pageView&#x27;)">
Views <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;pageView&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;options.expirationDate&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;options.expirationDate&#x27;)}" @click="setSort(&#x27;options.expirationDate&#x27;)">
Expires <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;options.expirationDate&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" class="col-link"><span class="sr-only">Actions</span></div>
</div>
<!-- Loading skeleton: keeps the layout stable while the three lists load -->
<template v-for="(i, index) in [1, 2, 3, 4]"><div class="paper-table-row paper-table-skeleton" role="row" aria-hidden="true" v-if="loading">
<div class="cell-anon" role="cell"><span class="skeleton skeleton-badge"></span><div class="anon-text"><span class="skeleton skeleton-line" style="width: 38%"></span><span class="skeleton skeleton-line skeleton-line-sm" style="width: 56%"></span></div></div>
<div class="cell-status" role="cell"><span class="skeleton skeleton-line" style="width: 60%"></span></div>
<div class="cell-views num" role="cell"><span class="skeleton skeleton-line" style="width: 40%"></span></div>
<div class="cell-expires" role="cell"><span class="skeleton skeleton-line" style="width: 55%"></span></div>
<div class="cell-actions" role="cell"></div>
</div></template>
<template v-for="(item, index) in filteredItems" :key="(item?._type + &#x27;:&#x27; + (item?._id || index))"><div class="paper-table-row" role="row" v-if="!loading" :class="{&#x27;repo-inactive&#x27;: item?._statusKey == &#x27;expired&#x27; || item?._statusKey == &#x27;removed&#x27;, &#x27;repo-error&#x27;: item?.status == &#x27;error&#x27;, &#x27;row-clickable&#x27;: !!item?._viewUrl}" @click="openItem(item, $event)">
<div class="cell-anon" role="cell">
<span class="project-type-icon" role="img" :aria-label="item?._type === 'repo' ? 'Repository' : item?._type === 'pr' ? 'Pull request' : 'Gist'" :title="item?._type === 'repo' ? 'Repository' : item?._type === 'pr' ? 'Pull request' : 'Gist'"><svg class="project-type-svg" aria-hidden="true"><use :href="'#project-type-' + item._type"></use></svg></span>
<div class="anon-text">
<div class="anon-title">
<a class="repo-name" v-if="item?._viewUrl" :title="item._label" :href="safeUrl((item?._viewUrl))"><template v-if="(item?._label || '').indexOf('/') > 0"><span class="rn-owner">{{ item._label.slice(0, item._label.indexOf('/') + 1) }}</span><wbr><span class="rn-repo">{{ item._label.slice(item._label.indexOf('/') + 1) }}</span></template><template v-else>{{ item?._label }}</template></a>
<span class="repo-name repo-name-static" v-if="!item?._viewUrl"><template v-if="(item?._label || '').indexOf('/') > 0"><span class="rn-owner">{{ item._label.slice(0, item._label.indexOf('/') + 1) }}</span><wbr><span class="rn-repo">{{ item._label.slice(item._label.indexOf('/') + 1) }}</span></template><template v-else>{{ item?._label }}</template></span>
<span class="type-badge type-coauthor" title="You are a co-author on this anonymization" v-if="item?.role === &#x27;coauthor&#x27;">Co-author</span>
</div>
<div class="anon-sub" v-if="item?._id || (item?._type === 'repo' &amp;&amp; item?.source?.commit) || item?.conference">
<code class="anonymous-id" v-if="item?._id" :title="item._id"><template v-if="item._id.length > 14"><span class="id-head">{{ item._id.slice(0, -6) }}</span><span class="id-tail">{{ item._id.slice(-6) }}</span></template><template v-else>{{ item._id }}</template></code>
<span class="commit-hash" v-if="item?._type === 'repo' &amp;&amp; item?.source?.commit" :title="item.source.commit">{{ item.source.commit.substring(0, 7) }}</span>
<span class="cell-conf conf-tag" v-if="item?.conference" :title="'Conference: ' + item.conference"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-conference"></use></svg><span class="conf-name" v-text="item?.conference"></span></span>
</div>
</div>
</div>
<div class="dashboard-row-meta" role="presentation">
<div class="cell-status" role="cell">
<div class="status-line">
<span class="status-dot" aria-hidden="true" :class="&#x27;status-&#x27; + item?._statusKey"></span>
<span class="status-word" v-text="fmt?.statusLabel(item?.status)"></span>
<template v-if="item?.status == 'removed'"><span class="seg-sep status-removed-sep" aria-hidden="true">·</span><button type="button" class="project-attention-action status-enable" @click.stop="refreshItem(item)" title="Reviewers can no longer open this link">Enable again</button></template>
</div>
<div class="status-sub status-sub-error" v-if="item?.status == &#x27;error&#x27; &amp;&amp; item?.statusMessage" v-text="fmt?.statusMsg(item?.statusMessage)" :title="(item?.statusMessage)"></div>
<div class="status-sub status-sub-warn" v-if="item?._stale" :title="&quot;Last activity &quot; + (fmt?.humanTime(item?.anonymizeDate || item?.lastView))">
{{ idleText(item) }}
</div>
<a class="project-attention-action" v-if="item.status === 'error' &amp;&amp; item._editUrl" :href="safeUrl(item._editUrl)">Fix source</a>
<div class="status-action-line" v-if="item._stale || item._broken"><span class="status-stuck" v-if="item._stale &amp;&amp; !item._broken">May be stuck<span aria-hidden="true"> &middot; </span></span><button type="button" class="project-attention-action" v-if="item._stale || item._broken" @click.stop="showProjectDetails(item)" :data-project-actions="item._broken ? encodeURIComponent(item._type + ':' + item._id) : null" :aria-controls="item._broken ? projectDetailsId(item) : null" :aria-expanded="item._broken ? selectedProject === item : null">{{ item._broken ? 'Review details' : 'Check progress' }}</button></div>
<button type="button" class="project-attention-action" v-if="item?.status == 'expired'" @click.stop="extendItem(item)" title="Reviewers can no longer open this link">Extend 6 months</button>
<div class="status-sub status-sub-date status-sub-removed" v-if="item?.status == 'removed' &amp;&amp; item?.anonymizeDate">anonymized {{ fmt?.humanTime(item?.anonymizeDate) }}</div>
<div class="status-sub status-sub-date" v-if="item?.status != &#x27;error&#x27; &amp;&amp; item?.status != &#x27;expired&#x27; &amp;&amp; item?.status != &#x27;removed&#x27; &amp;&amp; !item?._stale &amp;&amp; item?.anonymizeDate">
anonymized {{ fmt?.humanTime(item?.anonymizeDate) }}
</div>
</div>
<div class="cell-views num" role="cell" :data-unit="item.pageView === 1 ? 'view' : 'views'" :aria-label="fmt.number(item.pageView) + (item.pageView === 1 ? ' view' : ' views')"><svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg><span>{{ fmt.number(item.pageView) }}</span></div>
<div class="cell-expires" role="cell">
<svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
<span class="expires-never" v-if="item?._expiry?.kind === &#x27;never&#x27;">No expiry</span>
<span v-if="item?._expiry?.kind === &#x27;date&#x27; &amp;&amp; !item?._expiry?.soon" v-text="(fmt?.humanTime(item?._expiry?.date) || '').replace(/^on /, '')"></span>
<span class="expires-soon" v-if="item?._expiry?.kind === &#x27;date&#x27; &amp;&amp; item?._expiry?.soon" :title="'Expires ' + fmt?.humanTime(item?._expiry?.date)"><svg class="project-type-svg expires-soon-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg><span class="expires-soon-prefix">Expires </span>{{ item._expiry.soon }}</span>
<span class="expires-past" v-if="item?._expiry?.kind === &#x27;expired&#x27; &amp;&amp; item?._expiry?.date"><span class="expires-past-prefix">Expired </span>{{ item?._expiry?.label || fmt?.humanTime(item?._expiry?.date) }}</span>
<span class="expires-past" v-if="item?._expiry?.kind === &#x27;expired&#x27; &amp;&amp; !item?._expiry?.date">Expired</span>
<span class="empty-dash" aria-label="Not applicable" v-if="item?._expiry?.kind === &#x27;none&#x27;">&mdash;</span>
</div>
</div>
<div class="cell-actions" role="cell">
<button type="button" class="copy-url" v-if="item._viewUrl &amp;&amp; !(item?._statusKey == 'expired' || item?._statusKey == 'removed')" @click.stop="copyLink(item)" :disabled="!!copyingId" :title="'Copy link: ' + item._viewUrl" :aria-label="'Copy anonymous link for ' + item._label"><span class="sr-only" aria-live="polite">{{ copyingId === item._type + ':' + item._id ? 'Link copied' : '' }}</span><svg class="project-type-svg" aria-hidden="true"><use :href="copyingId === item._type + ':' + item._id ? '#ui-icon-check' : '#project-icon-copy'"></use></svg><span class="copy-url-label" aria-hidden="true">{{ copyingId === item._type + ':' + item._id ? 'Copied' : 'Copy link' }}</span></button>
<a class="row-edit row-open" v-if="item._viewUrl &amp;&amp; !(item?._statusKey == 'expired' || item?._statusKey == 'removed')" :href="safeUrl(item._viewUrl)" target="_blank" rel="noopener" :title="'Open what reviewers see'" :aria-label="'Open anonymous page for ' + item._label"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></a>
<a class="row-edit" v-if="item?._editUrl &amp;&amp; !item?._broken &amp;&amp; item?._statusKey != 'removed'" :href="safeUrl(item._editUrl)" :title="'Edit ' + item._name" :aria-label="'Edit ' + item._name"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg></a>
<button type="button" class="copy-url is-disabled" v-if="!(item._viewUrl &amp;&amp; !(item?._statusKey == 'expired' || item?._statusKey == 'removed')) &amp;&amp; item?._statusKey != 'removed' &amp;&amp; item?._statusKey != 'expired'" aria-disabled="true" @click.stop :title="(item?._statusKey == 'expired' ? 'Link expired: reviewers can no longer open it' : item?._statusKey == 'removed' ? 'Link offline: reviewers can no longer open it' : 'No link: this record is incomplete')" :aria-label="'Copy link unavailable for ' + item._label"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-copy"></use></svg><span class="copy-url-label" aria-hidden="true">Copy link</span></button>
<button type="button" class="row-edit row-open is-disabled" v-if="!(item._viewUrl &amp;&amp; !(item?._statusKey == 'expired' || item?._statusKey == 'removed')) &amp;&amp; item?._statusKey != 'removed' &amp;&amp; item?._statusKey != 'expired'" aria-disabled="true" @click.stop :title="(item?._statusKey == 'expired' ? 'Link expired: reviewers can no longer open it' : item?._statusKey == 'removed' ? 'Link offline: reviewers can no longer open it' : 'No link: this record is incomplete')" :aria-label="'Open unavailable for ' + item._label"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></button>
<button type="button" class="row-edit is-disabled" v-if="!(item?._editUrl &amp;&amp; !item?._broken)" aria-disabled="true" @click.stop title="Can't edit: this record is incomplete" :aria-label="'Edit unavailable for ' + item._label"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg></button>
<div class="dropdown is-disabled" v-if="item?._broken"><button type="button" class="btn btn-icon-dots is-disabled" aria-disabled="true" @click.stop title="No actions: this record is incomplete. Use Review details." :aria-label="'No actions available for ' + item._label"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-more"></use></svg></button></div>
<div class="dropdown" v-if="!item?._broken">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :data-project-actions="encodeURIComponent(item._type + ':' + item._id)" :aria-label="&quot;Actions for &quot; + (item?._name)">
<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<button type="button" class="dropdown-item" @click="showProjectDetails(item)" :aria-controls="projectDetailsId(item)" :aria-expanded="selectedProject === item">Project details &amp; name</button>
<a class="dropdown-item" :href="safeUrl((item?._viewUrl))">
View
</a>
<a class="dropdown-item" target="_self" v-if="item?._type === &#x27;repo&#x27; &amp;&amp; item?.options?.page &amp;&amp; item?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (item?.repoId) + &quot;/&quot;)">
View page
</a>
<button type="button" class="dropdown-item" @click="copyLink(item)" :disabled="!!copyingId" v-if="item?._viewUrl">
Copy anonymous link
</button>
<a class="dropdown-item" :href="safeUrl((item?._editUrl))">
Edit
</a>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;ready&#x27; || item?.status == &#x27;error&#x27;" @click="refreshItem(item)">
Force update
</button>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;removed&#x27;" @click="refreshItem(item)">
Enable
</button>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;expired&#x27;" @click="extendItem(item)">
Extend 6 months
</button>
<div v-if="(item?.status == &#x27;ready&#x27; || item?.status == &#x27;expired&#x27; || item?.status == &#x27;error&#x27;) &amp;&amp; item?.role !== &#x27;coauthor&#x27;">
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item dropdown-item-danger" @click="removeItem(item)">
Remove
</button>
</div>
</div>
</div>
</div>
</div>
<div class="project-details-row" role="row" v-if="!loading &amp;&amp; selectedProject === item">
<div class="project-details" role="cell" aria-colspan="5" :id="projectDetailsId(item)" tabindex="-1" @keydown.esc.stop="closeProjectDetails(item)">
<div class="project-details-heading"><h2>Project details</h2><button type="button" class="project-close" @click="closeProjectDetails(item)" aria-label="Close project details"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></div>
<dl class="project-details-list">
<div><dt>Source</dt><dd>{{ item._source || 'Not available' }}</dd></div>
<div><dt>Anonymous ID</dt><dd><code>{{ item._id || 'Missing identifier' }}</code></dd></div>
<div v-if="item.source?.commit"><dt>Commit</dt><dd><code>{{ item.source.commit }}</code></dd></div>
<div v-if="item.conference"><dt>Conference</dt><dd>{{ item.conference }}</dd></div>
<div><dt>Status</dt><dd>{{ fmt.statusLabel(item.status) }}<span v-if="item.statusMessage"> · {{ fmt.statusMsg(item.statusMessage) }}</span></dd></div>
<div v-if="item.anonymizeDate"><dt>Last anonymized</dt><dd>{{ fmt.humanTime(item.anonymizeDate) }}</dd></div>
</dl>
<p class="project-progress-help" v-if="item._stale">This download has not progressed recently. Check the source access and selected branch before retrying.</p>
<a class="project-attention-action" v-if="item._editUrl" :href="safeUrl(item._editUrl)">{{ needsAttention(item) ? 'Check source settings' : 'Edit anonymization settings' }}</a>
<form class="project-name-form" v-if="item._id" @submit.prevent="saveProjectName(item)">
<label :for="'project-name-' + item._type + '-' + item._id">Project name</label>
<p :id="'project-name-help-' + item._type + '-' + item._id">Only you see this name. Leave it blank to use the default.</p>
<div class="project-name-controls"><input class="form-control" type="text" :id="'project-name-' + item._type + '-' + item._id" :aria-describedby="'project-name-help-' + item._type + '-' + item._id" maxlength="100" v-model="projectName" :disabled="nameSaving"><button class="btn btn-ink" type="submit" :disabled="nameSaving">{{ nameSaving ? 'Saving…' : 'Save name' }}</button></div>
<p class="project-name-error" role="alert" v-if="nameError">{{ nameError }}</p><p role="status" v-if="nameMessage">{{ nameMessage }}</p>
</form>
</div>
</div></template>
<div class="paper-table-empty" v-if="!loading &amp;&amp; filteredItems?.length == 0">
<svg class="project-type-svg project-empty-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span v-if="!hasProjects()">You have no anonymizations yet.</span>
<span v-if="hasProjects()">Nothing matches the current filters.</span>
<div class="paper-table-empty-actions">
<button type="button" class="btn btn-outline-ink" v-if="hasActiveFilters()" @click="clearFilters()">Clear filters</button>
<a href="/anonymize" class="btn btn-ink" v-if="!hasProjects()"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New anonymization</a>
</div>
</div>
</div>
</div>
<div class="dashboard-pagination" v-if="dashboardCursor || dashboardError">
<p role="alert" v-if="dashboardError">{{ dashboardError }}</p>
<button type="button" class="btn" v-if="dashboardCursor" @click="loadMoreProjects()" :disabled="loadingMore">
{{ loadingMore ? 'Loading…' : 'Load more projects' }}
</button>
</div>
</div>