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>
137 lines
10 KiB
HTML
137 lines
10 KiB
HTML
<div class="container page paper-page conferences-page">
|
|
<div class="row">
|
|
<div class="w-100">
|
|
<div class="paper-crumbs"><a href="/dashboard">My work</a> / <span class="here">Conferences</span></div>
|
|
<div class="d-flex align-items-start justify-content-between flex-wrap page-head-row" 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">
|
|
<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 > 0" aria-label="Filter conferences" 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 conferences" placeholder="Search by name or ID…" autocomplete="off" v-field="{ state: viewState, set: value => { 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 => { 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 => { 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 => { 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 && filters?.status?.expired && filters?.status?.removed">All</span>
|
|
<span class="filter-btn-value" v-if="!(filters?.status?.ready && filters?.status?.expired && 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 => { 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>
|
|
</form>
|
|
|
|
<div class="dashboard-meta" v-if="conferences?.length > 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=""Show " + (statusLabels[f]) + " again"">×</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="{'repo-inactive': conference?.status == 'expired' || conference?.status == 'removed'}">
|
|
<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("/conference/" + (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"> · <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="'status-' + 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') }} – {{ fmt?.date(conference?.endDate, 'mediumDate') }}</span>
|
|
<span class="empty-dash" aria-label="No review window" v-if="!conference?.startDate">—</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=""Actions for " + (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("/conference/" + (conference?.conferenceID) + "/")">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View
|
|
</a>
|
|
<a class="dropdown-item" :href="safeUrl("/conference/" + (conference?.conferenceID) + "/edit")">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit
|
|
</a>
|
|
<div v-if="conference?.status != 'removed'">
|
|
<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 && !error && filteredConferences?.length == 0">
|
|
<svg class="ui-icon" aria-hidden="true"><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 > 0">Nothing matches the current filters.</span>
|
|
<div class="paper-table-empty-actions"><a href="/conference/new" class="btn btn-primary" v-if="false">New conference</a>
|
|
<button type="button" class="btn btn-outline-ink" v-if="conferences?.length > 0" @click="search = ''; filters.status.ready = true; filters.status.expired = true; filters.status.removed = true;">Clear filters</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|