mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 00:07:00 +01:00
Viewer: full-height app bar with a Help menu, search + ZIP sidebar, one toolbar for README and code, reading mode with a scroll-tracking outline, docked find for Markdown and code, and redaction slots (a blank stone slot holding the term number) in prose, headings, code, the tree and the path. Landing and FAQ: live redaction demo, serif display headings, teal accent. Author screens: My work table with copy/open/edit actions, a usage meter, a single-container New anonymization flow with a sticky submit bar, themed form controls, and consistent GitHub connection cards. Admin: underline tabs, severity-first errors chart and list, error tiles that filter (older pages load until matches appear), queue and overview health states, and shared toolbar heights and borders. Fixes: guarded localStorage access, failed README images render a placeholder, phone hover states no longer stick, and UI tests updated for the new copy and markup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
268 lines
26 KiB
HTML
268 lines
26 KiB
HTML
<div class="container page dashboard-page paper-page">
|
|
<div class="row">
|
|
<div class="w-100">
|
|
<div class="dashboard-heading">
|
|
<div>
|
|
<h1 class="paper-page-title">My work</h1>
|
|
<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" :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 && !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>
|
|
|
|
<!-- Search + filters -->
|
|
<form class="w-100 dashboard-filter-row" aria-label="Filter anonymizations" accept-charset="UTF-8" v-form="viewState" @submit.prevent="submitForm($event, () => { $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 => { 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="{'btn-primary': typeFilter === 'all'}" @click="typeFilter = 'all'" :aria-pressed="(typeFilter === 'all')">All</button>
|
|
<button type="button" class="btn dashboard-type-filter" title="Repositories" :class="{'btn-primary': typeFilter === 'repo'}" @click="typeFilter = 'repo'" :aria-pressed="(typeFilter === 'repo')"><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="{'btn-primary': typeFilter === 'pr'}" @click="typeFilter = 'pr'" :aria-pressed="(typeFilter === 'pr')"><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="{'btn-primary': typeFilter === 'gist'}" @click="typeFilter = 'gist'" :aria-pressed="(typeFilter === 'gist')"><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=""sort-" + (index)">
|
|
<label class="form-check-label" :for=""sort-" + (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">
|
|
<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" v-if="!hasHiddenStatus()">All</span>
|
|
<span class="filter-btn-value" v-if="hiddenStatusCount() === 1 && filters?.status?.removed === false">Active</span>
|
|
<span class="filter-btn-value" v-if="hasHiddenStatus() && !(hiddenStatusCount() === 1 && filters?.status?.removed === false)">{{ hiddenStatusCount() === 1 ? 'Hiding ' + (statusKeyLabels[Object.keys(filters?.status || {}).find(k => filters.status[k] === false)] || '').toLowerCase() : hiddenStatusCount() + ' hidden' }}</span>
|
|
</button>
|
|
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
|
|
<h6 class="dropdown-header">Show statuses</h6>
|
|
<div class="form-check dropdown-item" v-for="(label, key, index) in statusKeyLabels">
|
|
<input class="form-check-input" type="checkbox" v-field="{ state: viewState, set: value => { filters.status[key] = value }, value: filters?.status[key], form: null, options: {} }" :id=""status-" + (key)">
|
|
<label class="form-check-label" :for=""status-" + (key)">{{ label }}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- 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="attentionCount() > 0 || projectView === 'attention'">
|
|
<button type="button" :aria-pressed="projectView === 'all'" @click="setProjectView('all')" aria-label="All projects"><span class="dashboard-view-long">All projects</span><span class="dashboard-view-short" aria-hidden="true">All</span></button>
|
|
<button type="button" v-if="attentionCount() > 0 || projectView === 'attention'" :aria-pressed="projectView === 'attention'" @click="setProjectView('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>
|
|
</div>
|
|
<span class="dashboard-count">
|
|
<span v-if="(dashboardTotals?.filtered ?? filteredItems?.length) === (dashboardTotals?.total ?? items?.length)">{{ fmt?.number(dashboardTotals?.total ?? items?.length) }} project<span v-if="(dashboardTotals?.total ?? items?.length) !== 1">s</span></span>
|
|
<span v-else>{{ fmt?.number(dashboardTotals?.filtered ?? filteredItems?.length) }} shown · <button type="button" class="btn-link-inline hidden-toggle" @click="clearFilters()">Show {{ fmt?.number((dashboardTotals?.total ?? items?.length) - (dashboardTotals?.filtered ?? filteredItems?.length)) }} removed</button></span>
|
|
</span>
|
|
<div class="dashboard-active-filters">
|
|
<template v-for="(v, f, index) in filters?.status"><button type="button" class="filter-chip" v-if="v === false && !(f === 'removed' && hiddenStatusCount() === 1)" @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>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="paper-table paper-table-dashboard" role="table" aria-label="Anonymizations" :aria-busy="(loading)">
|
|
<div class="paper-table-head" role="row">
|
|
<div role="columnheader" :aria-sort="(isSortedBy('_label') ? (sortDesc() ? 'descending' : 'ascending') : 'none')">
|
|
<button type="button" class="sortable" :class="{active: isSortedBy('_label')}" @click="setSort('_label')">
|
|
Project <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy('_label')" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
|
|
</button>
|
|
</div>
|
|
<div role="columnheader" :aria-sort="(isSortedBy('status') ? (sortDesc() ? 'descending' : 'ascending') : 'none')">
|
|
<button type="button" class="sortable" :class="{active: isSortedBy('status')}" @click="setSort('status')">
|
|
Status <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy('status')" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
|
|
</button>
|
|
</div>
|
|
<div role="columnheader" class="num" :aria-sort="(isSortedBy('pageView') ? (sortDesc() ? 'descending' : 'ascending') : 'none')">
|
|
<button type="button" class="sortable" :class="{active: isSortedBy('pageView')}" @click="setSort('pageView')">
|
|
Views <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy('pageView')" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
|
|
</button>
|
|
</div>
|
|
<div role="columnheader" :aria-sort="(isSortedBy('options.expirationDate') ? (sortDesc() ? 'descending' : 'ascending') : 'none')">
|
|
<button type="button" class="sortable" :class="{active: isSortedBy('options.expirationDate')}" @click="setSort('options.expirationDate')">
|
|
Expires <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy('options.expirationDate')" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
|
|
</button>
|
|
</div>
|
|
<div role="columnheader" class="col-link">Link</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 + ':' + (item?._id || index))"><div class="paper-table-row" role="row" v-if="!loading" :class="{'repo-inactive': item?._statusKey == 'expired' || item?._statusKey == 'removed', 'repo-error': item?.status == 'error', 'row-clickable': !!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" v-text="item?._label" :href="safeUrl((item?._viewUrl))"></a>
|
|
<span class="repo-name repo-name-static" v-if="!item?._viewUrl" v-text="item?._label"></span>
|
|
<span class="type-badge type-coauthor" title="You are a co-author on this anonymization" v-if="item?.role === 'coauthor'">Co-author</span>
|
|
</div>
|
|
<div class="anon-sub">
|
|
<code class="anonymous-id" v-if="item?._id" :title="item._id">{{ item._id }}</code>
|
|
<span class="commit-hash" v-if="item?._type === 'repo' && item?.source?.commit" :title="item.source.commit">{{ item.source.commit.substring(0, 8) }}</span>
|
|
<span class="cell-conf conf-tag" v-if="item?.conference" :title="(item?.conference)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-conference"></use></svg> <span 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="'status-' + item?._statusKey"></span>
|
|
<span class="status-word" v-text="fmt?.statusLabel(item?.status)"></span>
|
|
</div>
|
|
<div class="status-sub status-sub-error" v-if="item?.status == 'error' && item?.statusMessage" v-text="fmt?.statusMsg(item?.statusMessage)" :title="(item?.statusMessage)"></div>
|
|
<div class="status-sub status-sub-warn" v-if="item?._stale" :title=""Last activity " + (fmt?.humanTime(item?.anonymizeDate || item?.lastView))">
|
|
Last activity {{ fmt?.humanTime(item?.anonymizeDate || item?.lastView) }} · may be stuck
|
|
</div>
|
|
<a class="project-attention-action" v-if="item.status === 'error' && item._editUrl" :href="safeUrl(item._editUrl)">Fix source</a>
|
|
<button type="button" class="project-attention-action" v-if="item._stale || item._broken" @click.stop="showProjectDetails(item)">{{ item._broken ? 'Review details' : 'Check progress' }}</button>
|
|
<div class="status-sub status-sub-date" v-if="item?.status != 'error' && !item?._stale && item?.anonymizeDate">
|
|
anonymized {{ fmt?.humanTime(item?.anonymizeDate) }}
|
|
</div>
|
|
</div>
|
|
<div class="cell-views num" role="cell" :aria-label="fmt.number(item.pageView) + ' 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 === 'never'">Never</span>
|
|
<span v-if="item?._expiry?.kind === 'date'" v-text="(fmt?.humanTime(item?._expiry?.date) || '').replace(/^on /, '')"></span>
|
|
<span class="expires-past" v-if="item?._expiry?.kind === 'expired' && item?._expiry?.date">Expired {{ fmt?.humanTime(item?._expiry?.date) }}</span>
|
|
<span class="expires-past" v-if="item?._expiry?.kind === 'expired' && !item?._expiry?.date">Expired</span>
|
|
<span class="empty-dash" aria-label="Not applicable" v-if="item?._expiry?.kind === 'none'">—</span>
|
|
</div>
|
|
</div>
|
|
<div class="cell-actions" role="cell">
|
|
<button type="button" class="copy-url" v-if="item._viewUrl" @click.stop="copyLink(item)" :disabled="!!copyingId" :title="'Copy anonymous link: ' + item._viewUrl" :aria-label="'Copy anonymous link for ' + item._label"><span class="copy-url-text">{{ copyingId === item._type + ':' + item._id ? 'Copied' : item._viewUrl.replace(/^https?:\/\/[^/]+/, '').replace(/\/$/, '') }}</span><svg class="project-type-svg" aria-hidden="true"><use :href="copyingId === item._type + ':' + item._id ? '#ui-icon-check' : '#project-icon-copy'"></use></svg></button>
|
|
<button type="button" class="project-details-button" v-if="item._broken" @click.stop="showProjectDetails(item)" :data-project-actions="encodeURIComponent(item._type + ':' + item._id)" :aria-controls="projectDetailsId(item)" :aria-expanded="selectedProject === item">Details</button>
|
|
<a class="row-edit row-open" v-if="item._viewUrl" :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 && !item?._broken" :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>
|
|
<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=""Actions for " + (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 & name</button>
|
|
<a class="dropdown-item" :href="safeUrl((item?._viewUrl))">
|
|
View
|
|
</a>
|
|
<a class="dropdown-item" target="_self" v-if="item?._type === 'repo' && item?.options?.page && item?.status == 'ready'" :href="safeUrl("/w/" + (item?.repoId) + "/")">
|
|
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 == 'ready' || item?.status == 'error'" @click="refreshItem(item)">
|
|
Force update
|
|
</button>
|
|
<button type="button" class="dropdown-item" v-if="item?.status == 'removed'" @click="refreshItem(item)">
|
|
Enable
|
|
</button>
|
|
<button type="button" class="dropdown-item" v-if="item?.status == 'expired'" @click="extendItem(item)">
|
|
Extend 6 months
|
|
</button>
|
|
<div v-if="(item?.status == 'ready' || item?.status == 'expired' || item?.status == 'error') && item?.role !== 'coauthor'">
|
|
<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 && 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 && 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>
|