Files
anonymous_github/public/partials/conferences.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

137 lines
10 KiB
HTML

<div class="container page paper-page conferences-page">
<div class="row">
<div class="w-100">
<div class="d-flex align-items-start justify-content-between flex-wrap page-head-row page-head page-head-main" style="gap: 12px;">
<div>
<h1 class="paper-page-title">Your <em>conferences</em></h1>
<p class="paper-page-lede">Group anonymizations by venue, give chairs a shared dashboard, and set one expiry for every submission.</p>
</div>
<a href="/conference/new" class="btn btn-primary" v-if="!(conferences &amp;&amp; conferences.length == 0)">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New conference
</a>
</div>
<form class="w-100 dashboard-filter-row" v-if="conferences?.length &gt; 0" aria-label="Filter conferences" 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 conferences" placeholder="Search by name or ID…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { search = value }, value: search, form: null, options: {} }">
</div>
<div class="dashboard-filter-controls">
<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">{{ ({name: 'Name', conferenceID: 'ID', '-status': 'Status'})[orderBy] || 'Custom' }}</span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Order by</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortName" value="name" v-field="{ state: viewState, set: value =&gt; { orderBy = value }, value: orderBy, form: null, options: {} }">
<label class="form-check-label" for="sortName">Name</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortID" value="conferenceID" v-field="{ state: viewState, set: value =&gt; { orderBy = value }, value: orderBy, form: null, options: {} }">
<label class="form-check-label" for="sortID">ID</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortStatus" value="-status" v-field="{ state: viewState, set: value =&gt; { orderBy = value }, value: orderBy, form: null, options: {} }">
<label class="form-check-label" for="sortStatus">Status</label>
</div>
</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="filters?.status?.ready &amp;&amp; filters?.status?.expired &amp;&amp; filters?.status?.removed">All</span>
<span class="filter-btn-value" v-if="!(filters?.status?.ready &amp;&amp; filters?.status?.expired &amp;&amp; filters?.status?.removed)">
{{ (filters?.status?.ready ? 0 : 1) + (filters?.status?.expired ? 0 : 1) + (filters?.status?.removed ? 0 : 1) }} 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 statusLabels">
<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>
</div>
</div>
</div>
</form>
<div class="dashboard-meta" v-if="conferences?.length &gt; 0">
<span class="dashboard-count">
<span v-if="filteredConferences?.length === conferences?.length">{{ fmt?.number(conferences?.length) }} conference<span v-if="conferences?.length !== 1">s</span></span>
<span v-if="filteredConferences?.length !== conferences?.length">{{ fmt?.number(filteredConferences?.length) }} of {{ fmt?.number(conferences?.length) }} shown</span>
</span>
<template v-for="(v, f, index) in filters?.status"><span class="filter-chip" v-if="v === false">
Hiding {{ statusLabels[f] }}
<button type="button" class="filter-chip-close" @click="filters.status[f] = true;" :aria-label="&quot;Show &quot; + (statusLabels[f]) + &quot; again&quot;">&times;</button>
</span></template>
</div>
</div>
<div class="conference-feedback" role="alert" v-if="error"><span>{{ error }}</span><button type="button" class="btn btn-outline-ink" @click="loadConferences()">Retry</button></div>
<div class="conference-feedback" role="status" v-if="loading">Loading conferences…</div>
<div class="paper-table paper-table-conferences w-100" role="table" aria-label="Conferences">
<div class="paper-table-head" role="row">
<div role="columnheader">Conference</div>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Repos</div>
<div role="columnheader">Review window</div>
<div role="columnheader"><span class="sr-only">Actions</span></div>
</div>
<div class="paper-table-row row-clickable" role="row" v-for="(conference, index) in filteredConferences" :key="conference?.conferenceID" :class="{&#x27;repo-inactive&#x27;: conference?.status == &#x27;expired&#x27; || conference?.status == &#x27;removed&#x27;}">
<div class="cell-anon" role="cell">
<svg class="ui-icon record-type-icon" role="img" aria-label="Conference"><use href="#project-icon-conference"></use></svg>
<div class="anon-text">
<a class="repo-name" v-text="conference?.name" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID))"></a>
<div class="anon-sub">
<span class="anon-source">ID <span class="commit-hash" v-text="conference?.conferenceID"></span><span v-if="conference?.url"> &middot; <a target="_blank" rel="noopener" v-text="fmt?.limitTo(conference?.url, 60)" :href="safeUrl((conference?.url))"></a></span></span>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<div class="status-line">
<span class="status-dot" aria-hidden="true" :class="&#x27;status-&#x27; + conference?.status"></span>
<span class="status-word" v-text="fmt?.statusLabel(conference?.status)"></span>
</div>
</div>
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> <span v-text="fmt?.number(conference?.nbRepositories || 0)"></span></div>
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
<span v-if="conference?.startDate">{{ fmt?.date(conference?.startDate, 'mediumDate') }} &ndash; {{ fmt?.date(conference?.endDate, 'mediumDate') }}</span>
<span class="empty-dash" aria-label="No review window" v-if="!conference?.startDate">&mdash;</span>
</div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :disabled="conference?.removing" :aria-label="&quot;Actions for &quot; + (conference?.name)">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/&quot;)">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View
</a>
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit
</a>
<div v-if="conference?.status != &#x27;removed&#x27;">
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item dropdown-item-danger" @click="removeConference(conference)">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove
</button>
</div>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="!loading &amp;&amp; !error &amp;&amp; filteredConferences?.length == 0">
<span class="empty-icon" aria-hidden="true" v-if="conferences?.length == 0"><svg class="ui-icon"><use href="#project-icon-conference"></use></svg></span>
<svg class="ui-icon" aria-hidden="true" v-if="conferences?.length &gt; 0"><use href="#project-icon-inbox"></use></svg>
<template v-if="conferences?.length == 0"><strong class="empty-title">No conferences yet</strong><span>Create one to group submissions for a venue, share a dashboard with chairs, and set a single expiry date.</span></template>
<span v-if="conferences?.length &gt; 0">Nothing matches the current filters.</span>
<div class="paper-table-empty-actions"><a href="/conference/new" class="btn btn-primary conf-empty-new" v-if="conferences?.length == 0"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New conference</a>
<button type="button" class="btn btn-outline-ink" v-if="conferences?.length &gt; 0" @click="search = &#x27;&#x27;; filters.status.ready = true; filters.status.expired = true; filters.status.removed = true;">Clear filters</button>
</div>
</div>
</div>
</div>
</div>