Accessibilty, first pass (#17)

* Placeholder color contrast @ WCAG 2.0 level AA

* Trailing whitespace removed.

* Remove redundant labels.

* Logo is a link in mobile, if not-embedded; otherwise, span. Fix link labels.

* HREF mandatory on A; menu behaves like a popup button.

* Hide icons from screen readers.

* About icon id keyboard-operarable and is labeled.

* Note about inaccessible modal.

* Missing closing tag.

* Fix nesting/indentation.

* Fix nesting/indentation; add missing closing tag.

* Trailing whitespace removed.

* Don't use NAV landmark for non-nav; add heading for page structure.

* Hide icon.

* Load archive form's buttons visibly receive keyboard focus.

* Trailing whitespace removed.

* "Hide" header button is keyboard-operable, and more-verbosely labeled for screenreader users.

* Don't use NAV landmark for non-nav; add heading for page structure.

* Sort order announced to screen reader users.

* Trailing whitespace removed.

* Delete button only a button when it can be pressed; announce in progress deletion.

* Trailing whitespace removed.

* Use a NAV landmark for replay controls.

* Remove "menu" role, to avoid having to implement arrow key support https://www.w3.org/WAI/GL/wiki/Using_ARIA_menus

* Hide icons.

* Label buttons.

* Links in disguise as buttons need HREF, and to respond to spacebar.

* Re-dub "main menu" to distinguish from replay menu

* Replay nav buttons visually get keyboard focus.

* Trailing whitespace removed.

* Hiding icons; button labels; links-as-buttons are operable.

* Use type=search for additional clarity.

* Add appropriate landmarks and a heading for structure and clarity.

* Tab selection is a nav, and is keyboard operable.

* Indentation; missing closing tag.

* Trailing whitespace removed.

* Title iframe.

* Not a navigation.

* Per discussion, rename tab to "URLs" (label only; route etc. the same while experimenting).

* Announce when expanded/collapsed.

* Remove "menu" to avoid having to implement expected keyboard support.

* Use correct heading level when in sidebar and when main.

* Trailing whitespace removed.

* Semantic markup of table; headings and captions; link-buttons are keyboard operable.

* Skip links for keyboard users.

* Title iframe; hide icon.

* Just a list, not an aside.

* Not an article.

* Remove trailing whitespace.

* Make each page in the list a heading.

* link-buttons are keyboard operable.

* Use semantic list for page list.

* Announce changes to search results.

* More wayfinding tweaks.

* Landmark and heading for replay.

* Dynamic page titles.

* Remove redundant labels from main landmarks.

* additional tweaks after rebase
- add 'role' to each of the tabs, dependent if sidebar or not
- add 'role=main' for replay
- add sr-only headers to each tab

* fix typos

* remaining tweaks:
- url-resources: fix sorter getting moved into notification
- url-resources: fix border for mobile view
- add screenreader labels to expand/hide buttons

* Show hover effect on focus.

* Wrap the whole sidebar is role=complementary, including the tab navigation.

* Make buttons keyboard operable; fix typos.

* Programmatically associate sidebar toggle button with sidebar.

* Per discusson, try calling this "URLs"

* Different heading levels for sidebar and main.

* Announce changes to search results.

* Correct dynamic page title, whe not in sidebar.

* Remove hyperlink focus decoration; reveal obscured focus indicator.

* Focus on replay, when replay changes.

* Make buttons keyboard operable.

* Move spacebar helper to misc instead of duplicating.

* Bump compiled assets.

* Use disabled attribute to match css; update label to describe new sidebar.

* Adjust replay focus styles.

* Adjust replay nav button focus, so visuals onclick in Chrome are less prominent.

* Put wordmark with logo on non-embed desktop, so that logo and wordmark make a single link.

Co-authored-by: Ilya Kreymer <ikreymer@gmail.com>
This commit is contained in:
Rebecca Lynn Cremona
2020-09-16 12:46:44 -07:00
committed by GitHub
co-authored by Ilya Kreymer
parent 7d9ce2b952
commit 05adead57c
15 changed files with 947 additions and 685 deletions
+71
View File
@@ -3,6 +3,7 @@
/*$wr-green: #64e986;*/ /*$wr-green: #64e986;*/
$wr-green: #55be6f; $wr-green: #55be6f;
$wr-yellow: #ffd975; $wr-yellow: #ffd975;
$wr-dark-yellow: #ffc42e;
$wr-light-blue: #43daff; $wr-light-blue: #43daff;
$wr-med-blue: #27a0ff; $wr-med-blue: #27a0ff;
$wr-dark-blue: #4876ff; $wr-dark-blue: #4876ff;
@@ -22,6 +23,76 @@ $dark: $wr-dark-green;
$navbar-breakpoint: 840px; $navbar-breakpoint: 840px;
// Set placeholder contrast to ~4.5:1 for WCAG AA compliance
$input-placeholder-color: #757575;
$input-disabled-placeholder-color: #707070;
.input::placeholder, .textarea::placeholder, .select select::placeholder {
opacity: 1;
}
// focus styles for links to match
a:focus {
color: #363636;
}
// focus/hover styles for buttons
.file-label:focus-within .file-cta {
background-color: #e8e8e8;
color: #363636;
}
.button.is-primary:focus, .button.is-primary:hover, .button.is-primary:active {
background-color: $dark !important;
color: white !important;
}
.button.is-warning:focus, .button.is-warning:hover, .button.is-warning:active {
background-color: $wr-dark-yellow !important;
color: black !important;
}
.replay-bar .button {
margin: 0 1px;
}
.replay-bar .button:focus {
box-shadow: none !important;
outline: 1px dotted $wr-dark-blue;
outline: -webkit-focus-ring-color auto 1px;
}
.skip-link {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute !important;
width: 1px;
word-wrap: normal !important; /* Many screen reader and browser combinations announce broken words as they would appear visually. */
&:focus {
background-color: black;
border-radius: 3px;
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
clip: auto !important;
clip-path: none;
color: white;
display: block;
font-family: inherit;
font-size: 1.3em;
font-weight: bold;
height: auto;
left: 5px;
line-height: normal;
padding: 15px 23px 14px;
text-decoration: none;
top: 5px;
width: auto;
z-index: 100000;
}
}
.main-scroll { .main-scroll {
flex: 1; flex: 1;
scroll-behavior: smooth; scroll-behavior: smooth;
+148 -113
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from 'lit-element';
import { wrapCss, rwpLogo, IS_APP } from './misc'; import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc';
import { registerSW, initDBWorker } from './pageutils'; import { registerSW, initDBWorker } from './pageutils';
@@ -23,6 +23,7 @@ class App extends LitElement
this.pageParams = {}; this.pageParams = {};
this.inited = false; this.inited = false;
this.navMenuShown = false;
registerSW(__SW_NAME__); registerSW(__SW_NAME__);
initDBWorker(); initDBWorker();
@@ -94,6 +95,7 @@ class App extends LitElement
.logo-text { .logo-text {
padding-left: 0px; padding-left: 0px;
margin-left: 8px;
} }
a.navbar-item.logo-text:hover { a.navbar-item.logo-text:hover {
@@ -103,7 +105,7 @@ class App extends LitElement
@media screen and (max-width: 840px) { @media screen and (max-width: 840px) {
.wide-only { .wide-only {
display: none; display: none !important;
} }
} }
@@ -116,133 +118,146 @@ class App extends LitElement
} }
return html` return html`
${!this.embed || this.embed === "full" ? html` ${!this.embed || this.embed === "full" ? html`
<nav class="navbar has-background-info" role="navigation" aria-label="main navigation"> <a href="#skip-main-target" @click=${this.skipMenu} class="skip-link">Skip main navigation</a>
<nav class="navbar has-background-info" aria-label="main">
<div class="navbar-brand"> <div class="navbar-brand">
${!this.embed ? html` ${!this.embed ? html `
<a class="navbar-item wr-logo-item" title="ReplayWeb.page" href="/"> <a href="/" class="navbar-item wr-logo-item" aria-labelledby="home">
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo}></fa-icon> <fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
</a>` : html` <span id="home" class="logo-text has-text-weight-bold is-size-5 has-allcaps wide-only">
<a class="navbar-item wr-logo-item" title="ReplayWeb.page"> <span class="has-text-primary">replay</span>
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo}></fa-icon> <span class="has-text-link">web.page</span>
</a> <span class="is-sr-only">Home</span>
</span>
</a>`: html `
<span class="navbar-item wr-logo-item">
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
</span>
`} `}
<a href="#" role="button" id="menu-button" @click="${this.onNavMenu}" @keyup="${clickOnSpacebarPress}"
<a role="button" @click="${this.onNavMenu}" class="navbar-burger burger ${this.navMenuShown ? 'is-active' : ''}" aria-label="main menu" aria-haspopup="true" aria-expanded="${this.navMenuShown}">
class="navbar-burger burger ${this.navMenuShown ? 'is-active' : ''}" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
</div> </div>
<div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}"> <div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}">
<div class="navbar-start"> <div class="navbar-start">
${!this.embed ? html` ${!this.embed ? html`
<a class="navbar-item logo-text has-text-weight-bold is-size-5 has-allcaps" href="/"> <a id="home" class="navbar-item logo-text has-text-weight-bold is-size-5 has-allcaps menu-only" href="/">
<span class="has-text-primary">replay</span> <span class="has-text-primary">replay</span>
<span class="has-text-link">web.page</span> <span class="has-text-link">web.page</span>
</a>` : ``} <span class="is-sr-only">Home</span>
</a>` : ``}
${IS_APP && !this.collInfo ? html` ${IS_APP && !this.collInfo ? html`
<a class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}"> <a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}" @keyup="${clickOnSpacebarPress}">
<fa-icon .svg="${fasArrowLeft}"></fa-icon><span class="menu-only">&nbsp;Go Back</span> <fa-icon .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only">&nbsp;Go Back</span>
</a> </a>
<a class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}"> <a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
<fa-icon .svg="${fasArrowRight}"></fa-icon><span class="menu-only">&nbsp;Go Forward</span> <fa-icon .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only">&nbsp;Go Forward</span>
</a> </a>
` : ``} ` : ``}
${this.sourceUrl && this.collInfo ? html` ${this.sourceUrl && this.collInfo ? html`
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
<a class="info-menu navbar-link is-arrowless"><fa-icon size="1.2em" .svg="${fasInfoIcon}"></fa-icon> <span tabindex="0" class="info-menu navbar-link is-arrowless" aria-labelledby="about-this-archive">
<span class="menu-only">About this Archive</span> <fa-icon size="1.2em" .svg="${fasInfoIcon}" aria-hidden="true"></fa-icon>
</a> <span id="about-this-archive" class="menu-only">About this Archive</span>
<div class="navbar-dropdown"> </span>
<div class="navbar-item wide-only"><i>About this Archive</i></div> <div class="navbar-dropdown">
<hr class="navbar-divider"> <div class="navbar-item wide-only"><i>About this Archive</i></div>
<div class="navbar-item"> <hr class="navbar-divider">
Source:&nbsp;<b>${this.sourceUrl}</b> <div class="navbar-item">
Source:&nbsp;<b>${this.sourceUrl}</b>
</div>
<div class="navbar-item">
Filename:&nbsp;<b>${this.collInfo.filename}</b>
</div>
<div class="navbar-item">
Total Size:&nbsp;<b>${prettyBytes(Number(this.collInfo.size || 0))}</b>
</div>
<div class="navbar-item">
Loading Mode:&nbsp;<b>${this.collInfo.onDemand ? "Download On-Demand" : "Fully Local"}</b>
</div>
<div class="navbar-item">
Collection Id:&nbsp;<b>${this.collInfo.coll}</b>
</div>
<hr class="navbar-divider">
<div class="navbar-item">
ReplayWeb.page Version: ${__VERSION__}
</div>
</div>
</div> </div>
<div class="navbar-item"> ` : ``}
Filename:&nbsp;<b>${this.collInfo.filename}</b>
</div>
<div class="navbar-item">
Total Size:&nbsp;<b>${prettyBytes(Number(this.collInfo.size || 0))}</b>
</div>
<div class="navbar-item">
Loading Mode:&nbsp;<b>${this.collInfo.onDemand ? "Download On-Demand" : "Fully Local"}</b>
</div>
<div class="navbar-item">
Collection Id:&nbsp;<b>${this.collInfo.coll}</b>
</div>
<hr class="navbar-divider">
<div class="navbar-item">
ReplayWeb.page Version: ${__VERSION__}
</div>
</div>
</div> </div>
` : ``} ${!this.embed ? html`
</div>
${!this.embed ? html`
<div class="navbar-end"> <div class="navbar-end">
<a href="/docs" target="_blank" class="navbar-item"> <a href="/docs" target="_blank" class="navbar-item">
<fa-icon .svg="${fasHelp}"></fa-icon><span>&nbsp;User Docs</span> <fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span>&nbsp;User Docs</span>
</a> </a>
<!--
-- NB: the Terms modal is currently inaccessible to people using keyboards or screen readers.
-- Should all the JS and infrastructure for accessible modals be added, or should Terms be a normal page?
-->
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showTerms = true} }"class="navbar-item">Terms</a> <a href="?terms" @click="${(e) => { e.preventDefault(); this.showTerms = true} }"class="navbar-item">Terms</a>
</div>` : html``} </div>` : html``}
</nav>
` : ''}
${this.sourceUrl ? html`
<wr-coll .loadInfo="${this.loadInfo}"
sourceUrl="${this.sourceUrl}"
embed="${this.embed}"
@replay-favicons=${this.onFavIcons}
@coll-loaded=${this.onCollLoaded}></wr-coll>
` : html`
<wr-coll-index>
${!IS_APP ? html`
<p slot="header" class="tagline is-size-5 has-text-centered">Explore and Replay Interactive Archived Webpages Directly in your Browser. <i><a target="_blank" href="./docs/examples">(See Examples)</a></i></p>
` : ``}
<wr-chooser slot="header" @load-start=${this.onStartLoad}></wr-chooser>
</wr-coll-index>
`}
${this.showTerms ? html`
<div class="modal is-active">
<div class="modal-background" @click="${(e) => this.showTerms = false}"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Terms and Privacy</p>
<button class="delete" aria-label="close" @click="${(e) => this.showTerms = false}"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div class="content">
<p>This site is a static browser-based application that loads web archive files provided by the user
and renders them for replay in the browser.</p>
<p>The site is operated by the <a href="https://webrecorder.net/">Webrecorder Project</a></p>
<p>See the <a target="_blank" href="./docs">Docs</a> for more info on how it works.</p>
<h3>Privacy</h3>
<p><b>No data is uploaded anywhere and no information is collected.</b></p>
<p>All content rendered stays directly in your browser. When loading an archive from Google Drive,
the site may ask for account authorization to download the specified file only.</p>
<h4>Disclaimer of Warranties</h4>
<p>The application may not always be available. No guarantees!</p>
<p>Some legalese:</p>
<p style="font-size: 0.8rem">DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS SOFTWARE TO YOU "AS AVAILABLE"
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
</p>
<a class="button is-warning" href="#" @click="${(e) => this.showTerms = false}">Close</a>
</div>
</div>
</section>
</div> </div>
</div> </nav><p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>
` : ``} ` ` : ''}
${this.sourceUrl ? html`
<wr-coll .loadInfo="${this.loadInfo}"
sourceUrl="${this.sourceUrl}"
embed="${this.embed}"
@replay-favicons=${this.onFavIcons}
@coll-loaded=${this.onCollLoaded}></wr-coll>
` : html`
<wr-coll-index>
${!IS_APP ? html`
<p slot="header" class="tagline is-size-5 has-text-centered">Explore and Replay Interactive Archived Webpages Directly in your Browser. <i><a target="_blank" href="./docs/examples">(See Examples)</a></i></p>
` : ``}
<wr-chooser slot="header" @load-start=${this.onStartLoad}></wr-chooser>
</wr-coll-index>
`}
${this.showTerms ? html`
<div class="modal is-active">
<div class="modal-background" @click="${(e) => this.showTerms = false}"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Terms and Privacy</p>
<button class="delete" aria-label="close" @click="${(e) => this.showTerms = false}"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div class="content">
<p>This site is a static browser-based application that loads web archive files provided by the user
and renders them for replay in the browser.</p>
<p>The site is operated by the <a href="https://webrecorder.net/">Webrecorder Project</a></p>
<p>See the <a target="_blank" href="./docs">Docs</a> for more info on how it works.</p>
<h3>Privacy</h3>
<p><b>No data is uploaded anywhere and no information is collected.</b></p>
<p>All content rendered stays directly in your browser. When loading an archive from Google Drive,
the site may ask for account authorization to download the specified file only.</p>
<h4>Disclaimer of Warranties</h4>
<p>The application may not always be available. No guarantees!</p>
<p>Some legalese:</p>
<p style="font-size: 0.8rem">DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS SOFTWARE TO YOU "AS AVAILABLE"
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
</p>
<a class="button is-warning" href="#" @click="${(e) => this.showTerms = false}">Close</a>
</div>
</div>
</section>
</div>
</div>
</div>
` : ``} `
} }
firstUpdated() { firstUpdated() {
@@ -274,13 +289,33 @@ class App extends LitElement
} }
} }
skipMenu(event){
// This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors.
event.preventDefault();
this.renderRoot.querySelector("#skip-main-target").focus()
}
onNavMenu(event) { onNavMenu(event) {
event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.navMenuShown = !this.navMenuShown; this.navMenuShown = !this.navMenuShown;
if (this.navMenuShown) { if (this.navMenuShown) {
document.addEventListener("click", () => { // Since this menu can be large and obscure significant page content,
// dismiss like a modal, returning keyboard focus to the
// menu button on dismissal.
document.addEventListener("click", (event) => {
event.preventDefault();
this.navMenuShown = false; this.navMenuShown = false;
this.renderRoot.querySelector("#menu-button").focus();
}, {once: true});
document.addEventListener("keypress", (event) => {
if (event.key == "Escape") {
event.preventDefault();
this.navMenuShown = false;
this.renderRoot.querySelector("#menu-button").focus();
}
}, {once: true}); }, {once: true});
} }
} }
+4 -4
View File
@@ -111,8 +111,8 @@ class Chooser extends LitElement
render() { render() {
return html` return html`
<section class="section less-padding"> <section class="section less-padding">
<nav class="panel"> <div class="panel">
<p class="panel-heading">Load Web Archive</p> <h2 class="panel-heading">Load Web Archive</h2>
<div class="extra-padding panel-block file has-name"> <div class="extra-padding panel-block file has-name">
<form class="content is-flex" @submit="${this.onStartLoad}"> <form class="content is-flex" @submit="${this.onStartLoad}">
<label class="file-label"> <label class="file-label">
@@ -121,7 +121,7 @@ class Chooser extends LitElement
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload"> @change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">
<span class="file-cta"> <span class="file-cta">
<span class="file-icon"> <span class="file-icon">
<fa-icon size="0.9em" .svg=${fasUpload}></fa-icon> <fa-icon size="0.9em" .svg=${fasUpload} aria-hidden="true"></fa-icon>
</span> </span>
<span class="file-label is-hidden-touch"> <span class="file-label is-hidden-touch">
Choose File... Choose File...
@@ -146,7 +146,7 @@ class Chooser extends LitElement
</form> </form>
</div> </div>
</nav> </div>
</section>`; </section>`;
} }
} }
+11 -11
View File
@@ -147,14 +147,14 @@ class CollIndex extends LitElement
.no-top-padding { .no-top-padding {
padding-top: 1.0em; padding-top: 1.0em;
} }
button.is-loading { .is-loading {
line-height: 1.5em; line-height: 1.5em;
height: 1.5em; height: 1.5em;
border: 0px; border: 0px;
background-color: transparent !important; background-color: transparent !important;
width: auto; width: auto;
} }
nav.panel.is-light { div.panel.is-light {
margin-bottom: 2em; margin-bottom: 2em;
} }
@@ -251,14 +251,14 @@ class CollIndex extends LitElement
</header> </header>
<section class="section no-top-padding"> <section class="section no-top-padding">
<div class="sort-header is-small"> <div class="sort-header is-small">
<a @click=${(e) => this.hideHeader = !this.hideHeader} class="collapse button is-small"> <button @click=${(e) => this.hideHeader = !this.hideHeader} class="collapse button is-small">
<span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span> <span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span>
<span>${this.hideHeader ? 'Show' : 'Hide'}</span> <span>${this.hideHeader ? 'Show ' : 'Hide'} <span class="is-sr-only">Header</span></span>
</a> </button>
</div> </div>
<nav class="panel is-light"> <div class="panel is-light">
<div class="panel-heading"><span>Loaded Archives</span> <h2 class="panel-heading"><span>Loaded Archives</span>
</div> </h2>
${this.colls.length ? html` ${this.colls.length ? html`
<div class="panel-block sort-header is-small"> <div class="panel-block sort-header is-small">
@@ -296,9 +296,9 @@ class CollIndex extends LitElement
</div> </div>
</div> </div>
${!this._deleting[coll.sourceUrl] ? html` ${!this._deleting[coll.sourceUrl] ? html`
<button class="delete" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button> <button class="delete" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
` : html` ` : html`
<button class="button delete is-loading is-static"></button>`} <span class="button delete is-loading is-static">Deleting</span`}
</div> </div>
`)} `)}
</div> </div>
@@ -309,7 +309,7 @@ class CollIndex extends LitElement
<i>No Archives so far! Archives loaded in the section above will appear here.</i> <i>No Archives so far! Archives loaded in the section above will appear here.</i>
</div> </div>
`} `}
</nav> </div>
</section> </section>
`; `;
} }
+117 -72
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css, query } from 'lit-element'; import { LitElement, html, css, query } from 'lit-element';
import { wrapCss, rwpLogo, IS_APP } from './misc'; import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc';
import { sourceToId, tsToDate, getPageDateTS } from './pageutils'; import { sourceToId, tsToDate, getPageDateTS } from './pageutils';
@@ -138,6 +138,10 @@ class Coll extends LitElement
this._replaceLoc = false; this._replaceLoc = false;
} else { } else {
window.location.hash = this._locationHash; window.location.hash = this._locationHash;
const replay = this.renderRoot.querySelector("wr-coll-replay");
if (replay) {
replay.focus();
}
} }
if (this.embed && window.parent !== window) { if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, '*'); window.parent.postMessage(this.tabData, '*');
@@ -290,7 +294,6 @@ class Coll extends LitElement
updateTabData(data, replaceLoc = false, merge = false) { updateTabData(data, replaceLoc = false, merge = false) {
this.tabData = {...this.tabData, ...data}; this.tabData = {...this.tabData, ...data};
if (this.tabData.url) { if (this.tabData.url) {
this.url = this.tabData.url || ""; this.url = this.tabData.url || "";
} }
@@ -465,7 +468,7 @@ class Coll extends LitElement
cursor: col-resize; cursor: col-resize;
} }
wr-coll-replay { main, wr-coll-replay {
width: 100%; width: 100%;
} }
@@ -483,12 +486,13 @@ class Coll extends LitElement
border-bottom: 0px; border-bottom: 0px;
} }
.sidebar-nav span { .sidebar-nav span.nav-hover {
font-size: smaller; font-size: smaller;
display: none; display: none;
} }
.sidebar-nav:hover span { .sidebar-nav:hover span.nav-hover,
.sidebar-nav:focus-within span.nav-hover {
display: initial; display: initial;
color: rgb(72, 118, 255); color: rgb(72, 118, 255);
} }
@@ -509,6 +513,16 @@ class Coll extends LitElement
right: 8px; right: 8px;
} }
/* Since the replay sometimes programmatically receives keyboard focus,
and that is visually unexpected for mouse-users, and since this won't
particularly trip up keyboard users, just remove the focus style. */
wr-coll-replay:focus {
outline: none;
}
/* Some keyboard-users may see this replacement style */
wr-coll-replay:focus-visible {
outline: 1px solid rgb(72, 118, 255);
}
`; `;
} }
@@ -518,6 +532,10 @@ class Coll extends LitElement
const isReplay = !!this.tabData.url; const isReplay = !!this.tabData.url;
const isSidebar = isReplay && this.showSidebar; const isSidebar = isReplay && this.showSidebar;
if (!isReplay){
document.title = `Browse Contents | ReplayWeb.page`;
}
if (this.collInfo && !this.collInfo.coll) { if (this.collInfo && !this.collInfo.coll) {
return html` return html`
<wr-loader .loadInfo="${this.loadInfo}" embed="${this.embed}" <wr-loader .loadInfo="${this.loadInfo}" embed="${this.embed}"
@@ -525,26 +543,27 @@ class Coll extends LitElement
} else if (this.collInfo) { } else if (this.collInfo) {
return html` return html`
${this.renderLocationBar()} ${this.renderLocationBar()}
<div id="tabContents"> <div id="tabContents">
<nav id="contents" class="is-light ${isSidebar ? 'sidebar' : (isReplay ? 'is-hidden' : 'full-pages')}"> <div id="contents" class="is-light ${isSidebar ? 'sidebar' : (isReplay ? 'is-hidden' : 'full-pages')}"
role="${isSidebar ? "complementary" : ""}" aria-label="${isSidebar ? "Browse Contents" : ""}">
${this.renderTabHeader(isSidebar)} ${this.renderTabHeader(isSidebar)}
${this.renderCollTabs(isSidebar)} ${this.renderCollTabs(isSidebar)}
</nav> </div>
${isReplay ? html` ${isReplay ? html`
<wr-coll-replay <wr-coll-replay
.collInfo="${this.collInfo}" role="main"
sourceUrl="${this.sourceUrl}" tabindex="-1"
url="${this.tabData.url || ""}" .collInfo="${this.collInfo}"
ts="${this.tabData.ts || ""}" sourceUrl="${this.sourceUrl}"
@coll-tab-nav="${this.onCollTabNav}" id="replay" url="${this.tabData.url || ""}"
@replay-loading="${(e) => this.isLoading = e.detail.loading}" ts="${this.tabData.ts || ""}"
class="${isReplay ? '' : 'is-hidden'}"> @coll-tab-nav="${this.onCollTabNav}" id="replay"
</wr-coll-replay> @replay-loading="${(e) => this.isLoading = e.detail.loading}"
class="${isReplay ? '' : 'is-hidden'}">
</wr-coll-replay>
` : ``} ` : ``}
</div> </div>
`; `;
} else { } else {
return html``; return html``;
@@ -557,46 +576,49 @@ class Coll extends LitElement
// } // }
return html` return html`
<div class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}"> <nav class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}" aria-label="tabs">
<ul> <ul>
${isSidebar ? html` ${isSidebar ? html`
<li class="sidebar-nav left"> <li class="sidebar-nav left">
<a @click="${this.onHideSidebar}" class="is-marginless is-size-6 is-paddingless"> <a role="button" href="#" @click="${this.onHideSidebar}" @keyup="${clickOnSpacebarPress}" class="is-marginless is-size-6 is-paddingless">
<fa-icon title="Hide" .svg="${fasAngleLeft}"></fa-icon><span>Hide</span> <fa-icon title="Hide" .svg="${fasAngleLeft}" aria-hidden="true"></fa-icon>
<span class="nav-hover" aria-hidden="true">Hide</span>
<span class="is-sr-only">Hide Sidebar</span>
</a> </a>
</li>` : ``} </li>` : ``}
${this.hasStory ? html` ${this.hasStory ? html`
<li class="${this.tabData.view === 'story' ? 'is-active' : ''}"> <li class="${this.tabData.view === 'story' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#story" class="is-size-6"> <a @click="${this.onTabClick}" href="#story" class="is-size-6" aria-label="Story" aria-current="${this.tabData.view === 'story' ? 'location' : ''}">
<span class="icon"><fa-icon .svg="${fasBook}"></fa-icon></span> <span class="icon"><fa-icon .svg="${fasBook}" aria-hidden="true" title="Story"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Story">Story</span> <span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Story">Story</span>
</a> </a>
</li>` : ``} </li>` : ``}
<li class="${this.tabData.view === 'pages' ? 'is-active' : ''}"> <li class="${this.tabData.view === 'pages' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#pages" class="is-size-6"> <a @click="${this.onTabClick}" href="#pages" class="is-size-6" aria-label="Pages" aria-current="${this.tabData.view === 'pages' ? 'location' : ''}">
<span class="icon"><fa-icon .svg="${farPages}"></fa-icon></span> <span class="icon"><fa-icon .svg="${farPages}" aria-hidden="true" title="Pages"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Pages">Pages</span> <span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Pages">Pages</span>
</a> </a>
</li> </li>
<li class="${this.tabData.view === 'resources' ? 'is-active' : ''}"> <li class="${this.tabData.view === 'resources' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#resources" class="is-size-6"> <a @click="${this.onTabClick}" href="#resources" class="is-size-6" aria-label="URLs" aria-current="${this.tabData.view === 'resources' ? 'location' : ''}">
<span class="icon"><fa-icon .svg="${farResources}"></fa-icon></span> <span class="icon"><fa-icon .svg="${farResources}" aria-hidden="true" title="URLs"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="URL Resources">Page Resources</span> <span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="URLs">URLs</span>
</a> </a>
</li> </li>
${isSidebar ? html` ${isSidebar ? html`
<li class="sidebar-nav right"> <li class="sidebar-nav right">
<a @click="${this.onFullPageView}" class="is-marginless is-size-6 is-paddingless"> <a role="button" href="#" @click="${this.onFullPageView}" @keyup="${clickOnSpacebarPress}" class="is-marginless is-size-6 is-paddingless">
<span>Expand</span><fa-icon title="Expand" .svg="${fasAngleRight}"></fa-icon> <span class="nav-hover" aria-hidden="true">Expand</span>
<span class="is-sr-only">Expand Sidebar to Full View</span>
<fa-icon title="Expand" .svg="${fasAngleRight}" aria-hidden="true"></fa-icon>
</a> </a>
</li>` : ``} </li>` : ``}
</ul> </ul>
</div>`; </nav>`;
} }
renderLocationBar() { renderLocationBar() {
@@ -609,84 +631,91 @@ class Coll extends LitElement
const isReplay = !!this.tabData.url; const isReplay = !!this.tabData.url;
return html` return html`
<div class="replay-bar"> <a class="skip-link" href="#skip-replay-target" @click="${this.skipMenu}">Skip replay navigation</a>
<nav class="replay-bar" aria-label="replay">
<div class="field has-addons"> <div class="field has-addons">
<button id="fullscreen" class="button is-borderless is-hidden-touch" @click="${this.onFullscreenToggle}"> <a href="#" role="button" id="fullscreen" class="button is-borderless is-hidden-touch" @click="${this.onFullscreenToggle}" @keyup="${clickOnSpacebarPress}"
title="${this.isFullscreen ? "Exit Full Screen" : "Full Screen"}" aria-label="${this.isFullscreen ? "Exit Fullscreen" : "Fullscreen"}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon>
</span> </span>
</button> </a>
<button class="button is-borderless is-hidden-mobile" @click="${this.onGoBack}"> <a href="#" role="button" class="button is-borderless is-hidden-mobile" @click="${this.onGoBack}" @keyup="${clickOnSpacebarPress}"
title="Back" aria-label="Back">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasLeft}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasLeft}"></fa-icon>
</span> </span>
</button> </a>
<button class="button is-borderless is-hidden-mobile" @click="${this.onGoForward}"> <a href="#" role="button" class="button is-borderless is-hidden-mobile" @click="${this.onGoForward}" @keyup="${clickOnSpacebarPress}"
title="Forward" aria-label="Forward">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasRight}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon>
</span> </span>
</button> </a>
<button id="refresh" class="button is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" @click="${this.onRefresh}"> <a href="#" role="button" id="refresh" class="button is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}"
title="Reload" aria-label="Reload">
<span class="icon is-small"> <span class="icon is-small">
${!this.isLoading ? html` ${!this.isLoading ? html`
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasRefresh}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRefresh}"></fa-icon>
` : ``} ` : ``}
</span> </span>
</button> </a>
<button class="button is-borderless is-hidden-touch ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}"> <a href="#" role="button" class="button is-borderless is-hidden-touch ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"
?disabled="${!isReplay}" title="Browse Contents" aria-label="Browse Contents" aria-controls="contents">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${farListAlt}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
</span> </span>
</button> </a>
<form @submit="${this.onSubmit}"> <form @submit="${this.onSubmit}">
<div class="control is-expanded"> <div class="control is-expanded">
<input id="url" class="input" type="text" @keydown="${this.onKeyDown}" .value="${this.url}" placeholder="Enter text to search or a URL to replay"/> <input id="url" class="input" type="search" @keydown="${this.onKeyDown}" .value="${this.url}" placeholder="Enter text to search or a URL to replay"/>
${isReplay ? html`<p id="datetime" class="control is-hidden-mobile">${dateStr}</p>` : html``} ${isReplay ? html`<p id="datetime" class="control is-hidden-mobile">${dateStr}</p>` : html``}
</div> </div>
</form> </form>
<div class="dropdown is-right ${this.menuActive ? 'is-active' : ''}" @click="${(e) => this.menuActive = false}"> <div class="dropdown is-right ${this.menuActive ? 'is-active' : ''}" @click="${(e) => this.menuActive = false}">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button class="button is-borderless" aria-haspopup="true" aria-controls="menu-dropdown" @click="${this.onMenu}"> <button class="button is-borderless" aria-haspopup="true" aria-controls="menu-dropdown" aria-expanded="${this.menuActive}" @click="${this.onMenu}"
aria-label="more replay controls">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasMenuV}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasMenuV}"></fa-icon>
</span> </span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="menu-dropdown" role="menu"> <div class="dropdown-menu" id="menu-dropdown">
<div class="dropdown-content"> <div class="dropdown-content">
<a class="dropdown-item is-hidden-desktop" @click="${this.onFullscreenToggle}"> <a href="#" role="button" class="dropdown-item is-hidden-desktop" @click="${this.onFullscreenToggle}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon>
</span> </span>
<span>Full Screen</span> <span>Full Screen</span>
</a> </a>
<a class="dropdown-item is-hidden-tablet" @click="${this.onGoBack}"> <a href="#" role="button" class="dropdown-item is-hidden-tablet" @click="${this.onGoBack}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasLeft}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasLeft}"></fa-icon>
</span> </span>
<span>Back</span> <span>Back</span>
</a> </a>
<a class="dropdown-item is-hidden-tablet" @click="${this.onGoForward}"> <a href="#" role="button" class="dropdown-item is-hidden-tablet" @click="${this.onGoForward}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasRight}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon>
</span> </span>
<span>Forward</span> <span>Forward</span>
</a> </a>
<a class="dropdown-item is-hidden-tablet" @click="${this.onRefresh}"> <a href="#" role="button" class="dropdown-item is-hidden-tablet" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${fasRefresh}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRefresh}"></fa-icon>
</span> </span>
<span>Reload</span> <span>Reload</span>
</a> </a>
<a class="dropdown-item is-hidden-desktop ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}"> <a href="#" role="button" class="dropdown-item is-hidden-desktop ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" .svg="${farListAlt}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
</span> </span>
<span>Page Search</span> <span>Browse Contents</span>
</a> </a>
<hr class="dropdown-divider is-hidden-desktop"> <hr class="dropdown-divider is-hidden-desktop">
<a class="dropdown-item" @click="${this.onPurgeCache}"> <a href="#" role="button" class="dropdown-item" @click="${this.onPurgeCache}" @keyup="${clickOnSpacebarPress}">
Purge Cache + Full Reload Purge Cache + Full Reload
</a> </a>
${dateStr ? html` ${dateStr ? html`
@@ -697,7 +726,7 @@ class Coll extends LitElement
${this.embed ? html` ${this.embed ? html`
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a href="https://replayweb.page/" target="_blank" class="dropdown-item info"> <a href="https://replayweb.page/" target="_blank" class="dropdown-item info">
<fa-icon class="menu-logo" size="1.0rem" .svg=${rwpLogo}></fa-icon> <fa-icon class="menu-logo" size="1.0rem" aria-hidden="true" .svg=${rwpLogo}></fa-icon>
<span>&nbsp;About ReplayWeb.page</span> <span>&nbsp;About ReplayWeb.page</span>
<span class="menu-version">(${__VERSION__})</span> <span class="menu-version">(${__VERSION__})</span>
</a> </a>
@@ -708,7 +737,7 @@ class Coll extends LitElement
</div> </div>
</div>`; </nav><p id="skip-replay-target" tabindex="-1" class="is-sr-only">Skipped</p>`;
} }
dragStart(event) { dragStart(event) {
@@ -737,7 +766,9 @@ class Coll extends LitElement
currList="${this.tabData.currList || 0}" currList="${this.tabData.currList || 0}"
@coll-tab-nav="${this.onCollTabNav}" id="story" @coll-tab-nav="${this.onCollTabNav}" id="story"
.isSidebar="${isSidebar}" .isSidebar="${isSidebar}"
class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"> class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
>
</wr-coll-story>` : ''} </wr-coll-story>` : ''}
<wr-coll-resources .collInfo="${this.collInfo}" <wr-coll-resources .collInfo="${this.collInfo}"
@@ -747,7 +778,9 @@ class Coll extends LitElement
.currMime="${this.tabData.currMime || ""}" .currMime="${this.tabData.currMime || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="resources" @coll-tab-nav="${this.onCollTabNav}" id="resources"
.isSidebar="${isSidebar}" .isSidebar="${isSidebar}"
class="is-paddingless ${isResources ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"> class="is-paddingless ${isResources ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
>
</wr-coll-resources> </wr-coll-resources>
<wr-page-view <wr-page-view
@@ -760,11 +793,20 @@ class Coll extends LitElement
.url="${this.tabData.url || ""}" .url="${this.tabData.url || ""}"
.ts="${this.tabData.ts || ""}" .ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="pages" @coll-tab-nav="${this.onCollTabNav}" id="pages"
class="${isPages ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"> class="${isPages ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
>
</wr-page-view> </wr-page-view>
`; `;
} }
skipMenu(event){
// This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors.
event.preventDefault();
this.renderRoot.querySelector("#skip-replay-target").focus()
}
onKeyDown(event) { onKeyDown(event) {
if (event.key === "Esc" || event.key === "Escape") { if (event.key === "Esc" || event.key === "Escape") {
event.target.value = this.url; event.target.value = this.url;
@@ -782,7 +824,8 @@ class Coll extends LitElement
} }
} }
onFullscreenToggle() { onFullscreenToggle(event) {
event.preventDefault();
this.menuActive = false; this.menuActive = false;
if (!this.isFullscreen) { if (!this.isFullscreen) {
this.requestFullscreen(); this.requestFullscreen();
@@ -791,12 +834,14 @@ class Coll extends LitElement
} }
} }
onGoBack() { onGoBack(event) {
event.preventDefault();
this.menuActive = false; this.menuActive = false;
window.history.back(); window.history.back();
} }
onGoForward() { onGoForward(event) {
event.preventDefault();
this.menuActive = false; this.menuActive = false;
window.history.forward(); window.history.forward();
} }
+5 -2
View File
@@ -168,13 +168,16 @@ class Embed extends LitElement
render() { render() {
return html` return html`
${this.paramString && this.hashString && this.swInited ? html` ${this.paramString && this.hashString && this.swInited ? html`
<iframe sandbox="allow-downloads allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-scripts allow-same-origin allow-forms" @load="${this.onLoad}" src="${this.replaybase}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen"></iframe> <iframe sandbox="allow-downloads allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-scripts allow-same-origin allow-forms"
@load="${this.onLoad}" src="${this.replaybase}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen"
title="Replay of ${this.title ? `${this.title}:` :``} ${this.url}"></iframe>
` : html``} ` : html``}
${this.noSW ? html` ${this.noSW ? html`
<section class="full-width"> <section class="full-width">
<div class="has-text-centered"> <div class="has-text-centered">
<fa-icon class="logo" id="wrlogo" size="2.5rem" .svg=${rwpLogo}></fa-icon> <fa-icon class="logo" id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
</div> </div>
<div class="error"> <div class="error">
Sorry, ReplayWeb.page won't work in this browser as Service Workers are not supported. Sorry, ReplayWeb.page won't work in this browser as Service Workers are not supported.
+12 -1
View File
@@ -18,6 +18,17 @@ function wrapCss(custom) {
const IS_APP = window.IS_APP || window.electron && window.electron.IS_APP || window.matchMedia('(display-mode: standalone)').matches; const IS_APP = window.IS_APP || window.electron && window.electron.IS_APP || window.matchMedia('(display-mode: standalone)').matches;
// ===========================================================================
// Buttons are expected to respond to both enter/return and spacebar.
// If using `<a>` with `role='button'`, assign this handler to keyup.
function clickOnSpacebarPress(event) {
if (event.key == " ") {
event.preventDefault();
event.target.click();
}
}
// =========================================================================== // ===========================================================================
class FaIcon extends LitElement class FaIcon extends LitElement
{ {
@@ -117,4 +128,4 @@ class AnimLogo extends FaIcon
customElements.define("fa-icon", FaIcon); customElements.define("fa-icon", FaIcon);
customElements.define("wr-anim-logo", AnimLogo); customElements.define("wr-anim-logo", AnimLogo);
export { wrapCss, IS_APP, rwpLogo, FaIcon, AnimLogo }; export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo };
+14 -3
View File
@@ -23,6 +23,7 @@ class PageEntry extends LitElement
this.iconValid = false; this.iconValid = false;
this.index = 0; this.index = 0;
this.isCurrent = false; this.isCurrent = false;
this.isSidebar = false;
this.timestamp = ""; this.timestamp = "";
this.date = null; this.date = null;
@@ -40,6 +41,7 @@ class PageEntry extends LitElement
iconValid: { type: Boolean }, iconValid: { type: Boolean },
index: { type: Number }, index: { type: Number },
isCurrent: { type: Boolean }, isCurrent: { type: Boolean },
isSidebar: { type: Boolean },
timestamp: { type: String }, timestamp: { type: String },
date: { type: Object } date: { type: Object }
} }
@@ -75,6 +77,15 @@ class PageEntry extends LitElement
width: 100%; width: 100%;
} }
/* Overrde Bulma to add the tiniest margin, so the focus indicator isn't obscured */
.columns {
margin-top: calc(-0.75rem + 2px);
}
.columns:last-child {
margin-bottom: calc(-0.75rem + 2px);
}
.favicon { .favicon {
width: 24px !important; width: 24px !important;
height: 24px !important; height: 24px !important;
@@ -189,7 +200,7 @@ class PageEntry extends LitElement
<div>${date ? date.toLocaleTimeString() : ""}</div> <div>${date ? date.toLocaleTimeString() : ""}</div>
</div> </div>
<div class="column"> <div class="column">
<article class="media"> <div class="media">
<figure class="media-left"> <figure class="media-left">
<p class=""> <p class="">
${this.iconValid ? html` ${this.iconValid ? html`
@@ -198,7 +209,7 @@ class PageEntry extends LitElement
</p> </p>
</figure> </figure>
<div class="media-content ${this.isCurrent ? 'current' : ''}"> <div class="media-content ${this.isCurrent ? 'current' : ''}">
<a @click="${this.onReplay}" href="#"> <div role="heading" aria-level="${this.isSidebar ? "4": "3"}"><a @click="${this.onReplay}" href="#">
${this.isCurrent ? html`<p class="curr-page is-pulled-right">Current Page</p>` : ``} ${this.isCurrent ? html`<p class="curr-page is-pulled-right">Current Page</p>` : ``}
<p class="is-size-6 has-text-weight-bold has-text-link text"> <p class="is-size-6 has-text-weight-bold has-text-link text">
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark> <keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
@@ -215,7 +226,7 @@ class PageEntry extends LitElement
<div class="media-right" style="margin-right: 2em"> <div class="media-right" style="margin-right: 2em">
${prettyBytes(p.size)} ${prettyBytes(p.size)}
</div>` : ``} </div>` : ``}
</article> </div>
</div> </div>
</div> </div>
+58 -66
View File
@@ -1,7 +1,7 @@
"use strict"; "use strict";
import { LitElement, html, css, unsafeCSS } from 'lit-element'; import { LitElement, html, css, unsafeCSS } from 'lit-element';
import { wrapCss } from './misc'; import { wrapCss, clickOnSpacebarPress } from './misc';
import ndjson from 'fetch-ndjson'; import ndjson from 'fetch-ndjson';
import FlexSearch from 'flexsearch'; import FlexSearch from 'flexsearch';
@@ -265,6 +265,10 @@ class Pages extends LitElement
box-sizing: border-box !important; box-sizing: border-box !important;
} }
div[role="main"], #contents div[role="complementary"] {
height: 100%;
}
.main.columns { .main.columns {
width: 100%; width: 100%;
justify-self: stretch; justify-self: stretch;
@@ -343,7 +347,6 @@ class Pages extends LitElement
max-height: 100%; max-height: 100%;
height: 100%; height: 100%;
} }
.index-bar-menu { .index-bar-menu {
max-height: 100%; max-height: 100%;
overflow-y: auto; overflow-y: auto;
@@ -467,64 +470,52 @@ class Pages extends LitElement
const currList = this.currList; const currList = this.currList;
return html` return html`
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "2": "1"}">
Pages in ${this.collInfo.title}
</div>
<div class="search-bar notification is-marginless"> <div class="search-bar notification is-marginless">
${this.isSidebar ? html `<h3 class="is-sr-only">Search and Filter Pages</h3>` : ``}
<div class="field flex-auto"> <div class="field flex-auto">
<div class="control has-icons-left ${this.loading ? 'is-loading' : ''}"> <div class="control has-icons-left ${this.loading ? 'is-loading' : ''}">
<input type="text" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text" <input type="search" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text"
placeholder="Search by Page URL, Title or Text"> placeholder="Search by Page URL, Title or Text">
<span class="icon is-left"><fa-icon .svg="${fasSearch}"/></span> <span class="icon is-left"><fa-icon .svg="${fasSearch}" aria-hidden="true"></fa-icon></span>
</div> </div>
</div> </div>
${this.isSidebar && this.collInfo.lists.length ? html`
<div class="is-hidden-tablet mobile-lists">
<span class="is-size-7">Filter By List:</span>
<div class="select is-small">
<select id="sort-select" @change=${this.onSelectListDrop}>
<option value="0" ?selected="${this.currList === 0}">All Pages</option>
${this.collInfo.lists.map(list => html`
<option value="${list.id}" ?selected="${this.currList === list.id}">${list.title}</option>
`)}
</select>
</div>
</div>` : ``}
</div> </div>
<div class="main columns">
<div class="column index-bar is-one-fifth is-hidden-mobile">
<div class="index-bar-title">${this.collInfo.title}</div>
<span class="num-results" aria-live="polite" aria-atomic="true">${this.formatResults()}</span>
<div class="main columns"> ${this.editable ? html`
<div class="column index-bar is-one-fifth is-hidden-mobile"> <div class="index-bar-actions">
<div class="index-bar-title">${this.collInfo.title}</div> ${this.renderDownloadMenu()}
</div>` : ``}
<span class="num-results">${this.formatResults()}</span> ${this.collInfo.lists.length ? html`
<p id="filter-label" class="menu-label">Filter By List:</p>
${this.editable ? html` <div class="index-bar-menu menu">
<div class="index-bar-actions"> <ul class="menu-list">
${this.renderDownloadMenu()} <li>
</div>` : ``} <a href="#list-0" data-list="0" class="${currList === 0 ? 'is-active' : ''}"
@click=${this.onSelectList}><i>All Pages</i></a>
${this.collInfo.lists.length ? html` </li>
<p id="filter-label" class="menu-label">Filter By List:</p> ${this.collInfo.lists.map(list => html`
<aside class="index-bar-menu menu">
<ul class="menu-list">
<li> <li>
<a href="#list-0" data-list="0" class="${currList === 0 ? 'is-active' : ''}" <a @click=${this.onSelectList} href="#list-${list.id}"
@click=${this.onSelectList}><i>All Pages</i></a> data-list="${list.id}"
</li> class="${currList === list.id ? 'is-active' : ''}">${list.title}</a>
${this.collInfo.lists.map(list => html` </li>`)}
<li> </ul>
<a @click=${this.onSelectList} href="#list-${list.id}"
data-list="${list.id}"
class="${currList === list.id ? 'is-active' : ''}">${list.title}</a>
</li>`)}
</ul>
</aside>
` : ``}
</div> </div>
<div class="column main-content"> ` : ``}
${this.renderPages()} </div>
</div> <div class="column main-content">
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "3": "2"}">Page List</div>
${this.renderPages()}
</div>
</div>`; </div>`;
} }
@@ -532,26 +523,26 @@ class Pages extends LitElement
return html` return html`
<div class="dropdown ${this.menuActive ? 'is-active' : ''}"> <div class="dropdown ${this.menuActive ? 'is-active' : ''}">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button @click="${this.onMenu}" class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu"> <button @click="${this.onMenu}" class="button is-small" aria-haspopup="true" aria-expanded="${this.menuActive}" aria-controls="dropdown-menu">
<span>Download</span> <span>Download</span>
<span class="icon is-small"> <span class="icon is-small">
<fa-icon .svg="${fasAngleDown}"/> <fa-icon .svg="${fasAngleDown} aria-hidden="true"></fa-icon>
</span> </span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu" role="menu"> <div class="dropdown-menu" id="dropdown-menu">
<div class="dropdown-content"> <div class="dropdown-content">
<a @click="${(e) => this.onDownload(e, "wacz", true)}" class="dropdown-item"> <a role="button" href="#" @click="${(e) => this.onDownload(e, "wacz", true)}" @keyup="${clickOnSpacebarPress}" class="dropdown-item">
Download Selected as WACZ (Web Archive Collection) Download Selected as WACZ (Web Archive Collection)
</a> </a>
<a @click="${(e) => this.onDownload(e, "warc", true)}" class="dropdown-item"> <a role="button" href="#" @click="${(e) => this.onDownload(e, "warc", true)}" @keyup="${clickOnSpacebarPress}" class="dropdown-item">
Download Selected as WARC Only Download Selected as WARC Only
</a> </a>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a @click="${(e) => this.onDownload(e, "wacz", false)}" class="dropdown-item"> <a role="button" href="#" @click="${(e) => this.onDownload(e, "wacz", false)}" @keyup="${clickOnSpacebarPress}" class="dropdown-item">
Download All as WACZ (Web Archive Collection) Download All as WACZ (Web Archive Collection)
</a> </a>
<a @click="${(e) => this.onDownload(e, "warc", false)}" class="dropdown-item"> <a role="button" href="#" @click="${(e) => this.onDownload(e, "warc", false)}" @keyup="${clickOnSpacebarPress}" class="dropdown-item">
Download All as WARC Only Download All as WARC Only
</a> </a>
</div> </div>
@@ -570,16 +561,16 @@ class Pages extends LitElement
<div class="header columns is-hidden-mobile"> <div class="header columns is-hidden-mobile">
${this.query ? html` ${this.query ? html`
<a @click="${this.onSort}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ''}">Match</a>` : ``} <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ''}">Match</a>` : ``}
<a @click="${this.onSort}" data-key="ts" class="column is-2 ${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a> <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="ts" class="column is-2 ${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a>
<a @click="${this.onSort}" data-key="title" class="column is-6 pagetitle ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a> <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="title" class="column is-6 pagetitle ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a>
</div> </div>
<div class="is-hidden-tablet mobile-header"> <div class="is-hidden-tablet mobile-header">
<div class="num-results">${this.formatResults()}</div> <div class="num-results" aria-live="polite" aria-atomic="true">${this.formatResults()}</div>
<wr-sorter id="pages" <wr-sorter id="pages"
.defaultKey="${this.sortKey ? this.sortKey : (this.query ? '' : 'ts')}" .defaultKey="${this.sortKey ? this.sortKey : (this.query ? '' : 'ts')}"
.defaultDesc="${this.sortDesc !== null ? this.sortDesc : (this.query ? false : true)}" .defaultDesc="${this.sortDesc !== null ? this.sortDesc : (this.query ? false : true)}"
@@ -618,18 +609,19 @@ class Pages extends LitElement
<div class="page-header has-text-weight-bold"> <div class="page-header has-text-weight-bold">
${this.renderPageHeader()} ${this.renderPageHeader()}
</div> </div>
<div class="scroller" @scroll="${this.onScroll}"> <ul class="scroller" @scroll="${this.onScroll}">
${this.sortedPages.length ? html` ${this.sortedPages.length ? html`
${this.sortedPages.map((p, i) => { ${this.sortedPages.map((p, i) => {
const isCurrPage = this.isCurrPage(p); const isCurrPage = this.isCurrPage(p);
return html` return html`
<div class="content ${isCurrPage ? 'current' : ''}"> <li class="content ${isCurrPage ? 'current' : ''}">
<wr-page-entry <wr-page-entry
.index="${this.query || this.isSidebar ? i + 1 : 0}" .index="${this.query || this.isSidebar ? i + 1 : 0}"
.editable="${this.editable}" .editable="${this.editable}"
.selected="${this.selectedPages.has(p.id)}" .selected="${this.selectedPages.has(p.id)}"
.isCurrent="${isCurrPage}" .isCurrent="${isCurrPage}"
.isSidebar="${this.isSidebar}"
.page="${p}" .page="${p}"
@sel-page="${this.onSelectToggle}" @sel-page="${this.onSelectToggle}"
@delete-page="${this.onDeletePage}" @delete-page="${this.onDeletePage}"
@@ -638,8 +630,8 @@ class Pages extends LitElement
class="${this.isSidebar ? 'sidebar' : ''}" class="${this.isSidebar ? 'sidebar' : ''}"
> >
</wr-page-entry> </wr-page-entry>
</div>` })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`} </li>` })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
</div> </ul>
`; `;
} }
@@ -740,7 +732,7 @@ class Pages extends LitElement
getNoResultsMessage() { getNoResultsMessage() {
if (!this.collInfo || !this.collInfo.pages.length) { if (!this.collInfo || !this.collInfo.pages.length) {
return html`No Pages defined this archive. Check out&nbsp;<a href="#view=resources">Page Resources</a>&nbsp;to search by URL.`; return html`<span class="fix-text-wrapping">No "Pages" are defined in this archive. <a href="#view=resources">Browse by URL</a>.</span>`;
} }
if (this.updatingSearch) { if (this.updatingSearch) {
@@ -751,8 +743,8 @@ class Pages extends LitElement
return "Searching..."; return "Searching...";
} }
if (!this.query) { if (this.query) {
return "No Pages Found. Try changing the search query."; return html `<span class="fix-text-wrapping">No matching pages found. Try changing the search query, or <a href="#view=resources">browse by URL</a>.</span>`;
} }
return "No Pages Found"; return "No Pages Found";
+16 -7
View File
@@ -112,6 +112,10 @@ class Replay extends LitElement
this.title = event.data.title; this.title = event.data.title;
} }
} }
if (this.title){
document.title = `Replay of ${this.title} | ReplayWeb.page`;
}
} }
waitForLoad() { waitForLoad() {
@@ -177,7 +181,7 @@ class Replay extends LitElement
flex-direction: column; flex-direction: column;
} }
nav.intro-panel.panel { div.intro-panel.panel {
min-width: 40%; min-width: 40%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -187,19 +191,23 @@ class Replay extends LitElement
} }
render() { render() {
const title = `Replay of ${this.title ? `${this.title}:` :``} ${this.url}`;
return html` return html`
<h1 id="replay-heading" class="is-sr-only">${title}</h1>
${this.iframeUrl ? html` ${this.iframeUrl ? html`
<iframe @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen <iframe @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen
src="${this.iframeUrl}"></iframe> src="${this.iframeUrl}" title="${title}"></iframe>
` : html` ` : html`
<nav class="panel intro-panel"> <div class="panel intro-panel">
<p class="panel-heading">Replay Web Page</p> <p class="panel-heading">Replay Web Page</p>
<div class="panel-block"> <div class="panel-block">
<p>Enter a URL above to replay it from the web archive!</p> <p>Enter a URL above to replay it from the web archive!</p>
<p>(Check out the <a href="#view=pages">Pages</a> or <a href="#view=resources">Page Resources</a> to find URLs in this archive.)</p> <p>(Or, check out <a href="#view=pages">Pages</a> or <a href="#view=resources">URLs</a> to explore the contents of this archive.)</p>
</div> </div>
</nav> </div>
`} `}
${this.isAuthable ? html` ${this.isAuthable ? html`
@@ -207,15 +215,16 @@ class Replay extends LitElement
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Auth Needed</p> <p class="modal-card-title">Auth Needed</p>
<button class="delete" aria-label="close"></button> <button class="delete" aria-label="close"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="container has-text-centered"> <div class="container has-text-centered">
<wr-gdrive .sourceUrl=${this.sourceUrl} .state="${this.showAuth ? 'trymanual' : 'implicitonly'}" .reauth="${true}" @load-ready=${this.onReAuthed}/> <wr-gdrive .sourceUrl=${this.sourceUrl} .state="${this.showAuth ? 'trymanual' : 'implicitonly'}" .reauth="${true}" @load-ready=${this.onReAuthed}/>
</div> </div>
</section> </section>
</div> </div>
</div>
</div> </div>
` : ``} ` : ``}
`; `;
+2 -1
View File
@@ -144,7 +144,8 @@ class Sorter extends LitElement
</div> </div>
<button @click=${(e) => this.sortDesc = !this.sortDesc} class="button is-small"> <button @click=${(e) => this.sortDesc = !this.sortDesc} class="button is-small">
<span>Order:</span> <span>Order:</span>
<span class="icon"><fa-icon .svg=${this.sortDesc ? fasSortUp : fasSortDown}></span> <span class="is-sr-only">${this.sortDesc ? "Ascending" : "Descending"}</span>
<span class="icon"><fa-icon aria-hidden="true" .svg=${this.sortDesc ? fasSortUp : fasSortDown}></span>
</button>`; </button>`;
} }
} }
+3 -1
View File
@@ -245,7 +245,9 @@ class Story extends LitElement
const currListNum = this.currList; const currListNum = this.currList;
return html` return html`
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "2": "1"}">
Story for ${this.collInfo.title}
</div>
<div class="columns"> <div class="columns">
<div class="column sidebar is-one-fifth"> <div class="column sidebar is-one-fifth">
<aside class="menu"> <aside class="menu">
+37 -21
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc'; import { wrapCss, clickOnSpacebarPress } from './misc';
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg'; import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg';
@@ -47,6 +47,7 @@ class URLResources extends LitElement
constructor() { constructor() {
super(); super();
this.collInfo = null; this.collInfo = null;
this.isSidebar = false;
this.currMime = ""; this.currMime = "";
this.query = ""; this.query = "";
@@ -69,6 +70,7 @@ class URLResources extends LitElement
static get properties() { static get properties() {
return { return {
collInfo: { type: Object }, collInfo: { type: Object },
isSidebar: { type: Boolean },
currMime: { type: String }, currMime: { type: String },
query: { type: String }, query: { type: String },
urlSearchType: { type: String }, urlSearchType: { type: String },
@@ -258,6 +260,12 @@ class URLResources extends LitElement
.columns { .columns {
margin: 0px; margin: 0px;
} }
thead {
margin-bottom: 24px;
}
table th:not([align]) {
text-align: left;
}
.result { .result {
border-bottom: 1px #dbdbdb solid; border-bottom: 1px #dbdbdb solid;
min-height: fit-content; min-height: fit-content;
@@ -266,6 +274,8 @@ class URLResources extends LitElement
border-bottom: 2px #dbdbdb solid; border-bottom: 2px #dbdbdb solid;
margin-right: 16px; margin-right: 16px;
min-height: fit-content; min-height: fit-content;
display: block;
width: 100%;
} }
.results-head a { .results-head a {
color: black; color: black;
@@ -298,6 +308,9 @@ class URLResources extends LitElement
render() { render() {
return html` return html`
<div role="heading" aria-level="${this.isSidebar ? "2": "1"}" class="is-sr-only">URLs in ${this.collInfo.title}</div>
<div role="heading" aria-level="${this.isSidebar ? "3": "2"}" class="is-sr-only">Search and Filter</div>
<div class="notification level is-marginless"> <div class="notification level is-marginless">
<div class="level-left flex-auto"> <div class="level-left flex-auto">
<div class="level-item flex-auto"> <div class="level-item flex-auto">
@@ -325,14 +338,13 @@ class URLResources extends LitElement
<label class="radio has-text-left"><input type="radio" name="urltype" value="" ?checked="${this.urlSearchType === 'contains'}" @click="${this.onClickUrlType}">&nbsp;Contains</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="" ?checked="${this.urlSearchType === 'contains'}" @click="${this.onClickUrlType}">&nbsp;Contains</label>
<label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === 'prefix'}" @click="${this.onClickUrlType}">&nbsp;Prefix</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === 'prefix'}" @click="${this.onClickUrlType}">&nbsp;Prefix</label>
<label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === 'exact'}" @click="${this.onClickUrlType}">&nbsp;Exact</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === 'exact'}" @click="${this.onClickUrlType}">&nbsp;Exact</label>
<span class="num-results" is-pulled-right">${this.filteredResults.length} Result(s)</span> <span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${this.filteredResults.length} Result(s)</span>
</div> </div>
</div> </div>
</div> </div>
<div class="sort-header is-hidden-tablet"> <div class="sort-header is-hidden-tablet">
<wr-sorter id="urls" <wr-sorter id="urls"
defaultKey="url"
defaultKey="${this.sortKey ? this.sortKey : "url"}" defaultKey="${this.sortKey ? this.sortKey : "url"}"
?defaultDesc="${this.sortDesc !== null ? this.sortDesc : false}" ?defaultDesc="${this.sortDesc !== null ? this.sortDesc : false}"
.sortKeys="${URLResources.sortKeys}" .sortKeys="${URLResources.sortKeys}"
@@ -341,27 +353,31 @@ class URLResources extends LitElement
</wr-sorter> </wr-sorter>
</div> </div>
<div class="all-results"> <div role="heading" aria-level="${this.isSidebar ? "3": "2"}" id="results-heading" class="is-sr-only">Results</div>
<div class="columns results-head has-text-weight-bold">
<a @click="${this.onSort}" data-key="url" class="column col-url is-6 is-hidden-mobile ${this.sortKey === "url" ? (this.sortDesc ? "desc" : "asc") : ''}">URL</a>
<a @click="${this.onSort}" data-key="ts" class="column col-ts is-2 is-hidden-mobile ${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a>
<a @click="${this.onSort}" data-key="mime" class="column col-mime is-3 is-hidden-mobile ${this.sortKey === "mime" ? (this.sortDesc ? "desc" : "asc") : ''}">Mime Type</a>
<a @click="${this.onSort}" data-key="status" class="column col-status is-1 is-hidden-mobile ${this.sortKey === "status" ? (this.sortDesc ? "desc" : "asc") : ''}">Status</a>
</div>
<div class="main-scroll" @scroll="${this.onScroll}"> <table class="all-results" aria-labelledby="results-heading num-results">
<thead>
<tr class="columns results-head has-text-weight-bold">
<th scope="col" class="column col-url is-6 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="url" class="${this.sortKey === "url" ? (this.sortDesc ? "desc" : "asc") : ''}">URL</a></th>
<th scope="col" class="column col-ts is-2 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="ts" class="${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a></th>
<th scope="col" class="column col-mime is-3 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="mime" class="${this.sortKey === "mime" ? (this.sortDesc ? "desc" : "asc") : ''}">Mime Type</a></th>
<th scope="col" class="column col-status is-1 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="status" class="${this.sortKey === "status" ? (this.sortDesc ? "desc" : "asc") : ''}">Status</a></th>
</tr>
</thead>
<tbody class="main-scroll" @scroll="${this.onScroll}">
${this.sortedResults.length ? ${this.sortedResults.length ?
this.sortedResults.map((result) => html` this.sortedResults.map((result) => html`
<div class="columns result"> <tr class="columns result">
<div class="column col-url is-6"><p class="minihead is-hidden-tablet">URL</p><a @click="${this.onReplay}" data-url="${result.url}" data-ts="${result.ts}" href="#">${result.url}</a></div> <td class="column col-url is-6"><p class="minihead is-hidden-tablet">URL</p><a @click="${this.onReplay}" data-url="${result.url}" data-ts="${result.ts}" href="#">${result.url}</a></td>
<div class="column col-ts is-2"><p class="minihead is-hidden-tablet">Date</p>${new Date(result.date).toLocaleString()}</div> <td class="column col-ts is-2"><p class="minihead is-hidden-tablet">Date</p>${new Date(result.date).toLocaleString()}</td>
<div class="column col-mime is-3"><p class="minihead is-hidden-tablet">Mime Type</p>${result.mime}</div> <td class="column col-mime is-3"><p class="minihead is-hidden-tablet">Mime Type</p>${result.mime}</td>
<div class="column col-status is-1"><p class="minihead is-hidden-tablet">Status</p>${result.status}</div> <td class="column col-status is-1"><p class="minihead is-hidden-tablet">Status</p>${result.status}</td>
</div> </tr>
`) : html`<div class="section"><i>No Results Found.</i></div>`} `) : html`<tr class="section"><td colspan="4"><i>No Results Found.</i></td></tr>`}
</div> </tbody>
</div> </table>
`; `;
} }
onSort(event) { onSort(event) {
+3 -3
View File
File diff suppressed because one or more lines are too long
+400 -334
View File
File diff suppressed because one or more lines are too long