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:
co-authored by
Ilya Kreymer
parent
7d9ce2b952
commit
05adead57c
+158
-123
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -23,6 +23,7 @@ class App extends LitElement
|
||||
this.pageParams = {};
|
||||
|
||||
this.inited = false;
|
||||
this.navMenuShown = false;
|
||||
|
||||
registerSW(__SW_NAME__);
|
||||
initDBWorker();
|
||||
@@ -77,7 +78,7 @@ class App extends LitElement
|
||||
.tagline {
|
||||
margin-top: 1.0rem;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 840px) {
|
||||
.menu-only {
|
||||
display: none;
|
||||
@@ -94,6 +95,7 @@ class App extends LitElement
|
||||
|
||||
.logo-text {
|
||||
padding-left: 0px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
a.navbar-item.logo-text:hover {
|
||||
@@ -103,7 +105,7 @@ class App extends LitElement
|
||||
|
||||
@media screen and (max-width: 840px) {
|
||||
.wide-only {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,133 +118,146 @@ class App extends LitElement
|
||||
}
|
||||
return 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">
|
||||
${!this.embed ? html`
|
||||
<a class="navbar-item wr-logo-item" title="ReplayWeb.page" href="/">
|
||||
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo}></fa-icon>
|
||||
</a>` : html`
|
||||
<a class="navbar-item wr-logo-item" title="ReplayWeb.page">
|
||||
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo}></fa-icon>
|
||||
</a>
|
||||
${!this.embed ? html `
|
||||
<a href="/" class="navbar-item wr-logo-item" aria-labelledby="home">
|
||||
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
|
||||
<span id="home" class="logo-text has-text-weight-bold is-size-5 has-allcaps wide-only">
|
||||
<span class="has-text-primary">replay</span>
|
||||
<span class="has-text-link">web.page</span>
|
||||
<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 role="button" @click="${this.onNavMenu}"
|
||||
class="navbar-burger burger ${this.navMenuShown ? 'is-active' : ''}" aria-label="menu" aria-expanded="false">
|
||||
<a href="#" role="button" id="menu-button" @click="${this.onNavMenu}" @keyup="${clickOnSpacebarPress}"
|
||||
class="navbar-burger burger ${this.navMenuShown ? 'is-active' : ''}" aria-label="main menu" aria-haspopup="true" aria-expanded="${this.navMenuShown}">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}">
|
||||
<div class="navbar-start">
|
||||
${!this.embed ? html`
|
||||
<a class="navbar-item logo-text has-text-weight-bold is-size-5 has-allcaps" href="/">
|
||||
<span class="has-text-primary">replay</span>
|
||||
<span class="has-text-link">web.page</span>
|
||||
</a>` : ``}
|
||||
<div class="navbar-start">
|
||||
${!this.embed ? html`
|
||||
<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-link">web.page</span>
|
||||
<span class="is-sr-only">Home</span>
|
||||
</a>` : ``}
|
||||
|
||||
${IS_APP && !this.collInfo ? html`
|
||||
<a class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}">
|
||||
<fa-icon .svg="${fasArrowLeft}"></fa-icon><span class="menu-only"> Go Back</span>
|
||||
</a>
|
||||
<a class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}">
|
||||
<fa-icon .svg="${fasArrowRight}"></fa-icon><span class="menu-only"> Go Forward</span>
|
||||
</a>
|
||||
` : ``}
|
||||
${this.sourceUrl && this.collInfo ? html`
|
||||
<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 class="menu-only">About this Archive</span>
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<div class="navbar-item wide-only"><i>About this Archive</i></div>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item">
|
||||
Source: <b>${this.sourceUrl}</b>
|
||||
${IS_APP && !this.collInfo ? html`
|
||||
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}" @keyup="${clickOnSpacebarPress}">
|
||||
<fa-icon .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only"> Go Back</span>
|
||||
</a>
|
||||
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
|
||||
<fa-icon .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only"> Go Forward</span>
|
||||
</a>
|
||||
` : ``}
|
||||
${this.sourceUrl && this.collInfo ? html`
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<span tabindex="0" class="info-menu navbar-link is-arrowless" aria-labelledby="about-this-archive">
|
||||
<fa-icon size="1.2em" .svg="${fasInfoIcon}" aria-hidden="true"></fa-icon>
|
||||
<span id="about-this-archive" class="menu-only">About this Archive</span>
|
||||
</span>
|
||||
<div class="navbar-dropdown">
|
||||
<div class="navbar-item wide-only"><i>About this Archive</i></div>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item">
|
||||
Source: <b>${this.sourceUrl}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Filename: <b>${this.collInfo.filename}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Total Size: <b>${prettyBytes(Number(this.collInfo.size || 0))}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Loading Mode: <b>${this.collInfo.onDemand ? "Download On-Demand" : "Fully Local"}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Collection Id: <b>${this.collInfo.coll}</b>
|
||||
</div>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item">
|
||||
ReplayWeb.page Version: ${__VERSION__}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Filename: <b>${this.collInfo.filename}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Total Size: <b>${prettyBytes(Number(this.collInfo.size || 0))}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Loading Mode: <b>${this.collInfo.onDemand ? "Download On-Demand" : "Fully Local"}</b>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
Collection Id: <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`
|
||||
${!this.embed ? html`
|
||||
<div class="navbar-end">
|
||||
<a href="/docs" target="_blank" class="navbar-item">
|
||||
<fa-icon .svg="${fasHelp}"></fa-icon><span> User Docs</span>
|
||||
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span> User Docs</span>
|
||||
</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>
|
||||
</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>
|
||||
` : ``} `
|
||||
</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() {
|
||||
@@ -259,28 +274,48 @@ class App extends LitElement
|
||||
}
|
||||
|
||||
onFavIcons(event) {
|
||||
const head = document.querySelector('head');
|
||||
const head = document.querySelector('head');
|
||||
const oldLinks = document.querySelectorAll("link[rel*='icon']");
|
||||
|
||||
for (const link of oldLinks) {
|
||||
head.removeChild(link);
|
||||
}
|
||||
for (const link of oldLinks) {
|
||||
head.removeChild(link);
|
||||
}
|
||||
|
||||
for (const icon of event.detail.icons) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = icon.rel;
|
||||
link.href = icon.href;
|
||||
head.appendChild(link);
|
||||
const link = document.createElement('link');
|
||||
link.rel = icon.rel;
|
||||
link.href = icon.href;
|
||||
head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.navMenuShown = !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.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});
|
||||
}
|
||||
}
|
||||
@@ -398,4 +433,4 @@ class App extends LitElement
|
||||
|
||||
customElements.define("replay-app-main", App);
|
||||
|
||||
export { App };
|
||||
export { App };
|
||||
|
||||
+7
-7
@@ -45,9 +45,9 @@ class Chooser extends LitElement
|
||||
loadInfo.noCache = this.file.path !== undefined;
|
||||
loadInfo.name = this.fileDisplayName;
|
||||
}
|
||||
|
||||
|
||||
this.dispatchEvent(new CustomEvent("load-start", {detail: loadInfo}));
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ class Chooser extends LitElement
|
||||
render() {
|
||||
return html`
|
||||
<section class="section less-padding">
|
||||
<nav class="panel">
|
||||
<p class="panel-heading">Load Web Archive</p>
|
||||
<div class="panel">
|
||||
<h2 class="panel-heading">Load Web Archive</h2>
|
||||
<div class="extra-padding panel-block file has-name">
|
||||
<form class="content is-flex" @submit="${this.onStartLoad}">
|
||||
<label class="file-label">
|
||||
@@ -121,7 +121,7 @@ class Chooser extends LitElement
|
||||
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">
|
||||
<span class="file-cta">
|
||||
<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 class="file-label is-hidden-touch">
|
||||
Choose File...
|
||||
@@ -146,11 +146,11 @@ class Chooser extends LitElement
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("wr-chooser", Chooser);
|
||||
|
||||
export { Chooser };
|
||||
export { Chooser };
|
||||
|
||||
+14
-14
@@ -94,7 +94,7 @@ class CollIndex extends LitElement
|
||||
const resp = await fetch("./wabac/api/index");
|
||||
try {
|
||||
const json = await resp.json();
|
||||
this.colls = json.colls.map((coll) => {
|
||||
this.colls = json.colls.map((coll) => {
|
||||
coll.title = coll.title || coll.filename;
|
||||
return coll;
|
||||
});
|
||||
@@ -147,14 +147,14 @@ class CollIndex extends LitElement
|
||||
.no-top-padding {
|
||||
padding-top: 1.0em;
|
||||
}
|
||||
button.is-loading {
|
||||
.is-loading {
|
||||
line-height: 1.5em;
|
||||
height: 1.5em;
|
||||
border: 0px;
|
||||
background-color: transparent !important;
|
||||
width: auto;
|
||||
}
|
||||
nav.panel.is-light {
|
||||
div.panel.is-light {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
@@ -251,14 +251,14 @@ class CollIndex extends LitElement
|
||||
</header>
|
||||
<section class="section no-top-padding">
|
||||
<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>${this.hideHeader ? 'Show' : 'Hide'}</span>
|
||||
</a>
|
||||
<span>${this.hideHeader ? 'Show ' : 'Hide'} <span class="is-sr-only">Header</span></span>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="panel is-light">
|
||||
<div class="panel-heading"><span>Loaded Archives</span>
|
||||
</div>
|
||||
<div class="panel is-light">
|
||||
<h2 class="panel-heading"><span>Loaded Archives</span>
|
||||
</h2>
|
||||
|
||||
${this.colls.length ? html`
|
||||
<div class="panel-block sort-header is-small">
|
||||
@@ -292,13 +292,13 @@ class CollIndex extends LitElement
|
||||
</div>
|
||||
<div class="column is-2"><p class="minihead">Date Loaded</p>${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}</div>
|
||||
<div class="column is-2"><p class="minihead">Total Size</p>${prettyBytes(Number(coll.size || 0))}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
${!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`
|
||||
<button class="button delete is-loading is-static"></button>`}
|
||||
<span class="button delete is-loading is-static">Deleting</span`}
|
||||
</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>
|
||||
</div>
|
||||
`}
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@@ -325,4 +325,4 @@ class CollIndex extends LitElement
|
||||
|
||||
customElements.define("wr-coll-index", CollIndex);
|
||||
|
||||
export { CollIndex };
|
||||
export { CollIndex };
|
||||
|
||||
+120
-75
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -84,7 +84,7 @@ class Coll extends LitElement
|
||||
|
||||
url: { type: String },
|
||||
ts: { type: String },
|
||||
|
||||
|
||||
isFullscreen: { type: Boolean },
|
||||
menuActive: { type: Boolean },
|
||||
|
||||
@@ -138,6 +138,10 @@ class Coll extends LitElement
|
||||
this._replaceLoc = false;
|
||||
} else {
|
||||
window.location.hash = this._locationHash;
|
||||
const replay = this.renderRoot.querySelector("wr-coll-replay");
|
||||
if (replay) {
|
||||
replay.focus();
|
||||
}
|
||||
}
|
||||
if (this.embed && window.parent !== window) {
|
||||
window.parent.postMessage(this.tabData, '*');
|
||||
@@ -290,7 +294,6 @@ class Coll extends LitElement
|
||||
|
||||
updateTabData(data, replaceLoc = false, merge = false) {
|
||||
this.tabData = {...this.tabData, ...data};
|
||||
|
||||
if (this.tabData.url) {
|
||||
this.url = this.tabData.url || "";
|
||||
}
|
||||
@@ -465,7 +468,7 @@ class Coll extends LitElement
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
wr-coll-replay {
|
||||
main, wr-coll-replay {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -483,12 +486,13 @@ class Coll extends LitElement
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.sidebar-nav span {
|
||||
.sidebar-nav span.nav-hover {
|
||||
font-size: smaller;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-nav:hover span {
|
||||
.sidebar-nav:hover span.nav-hover,
|
||||
.sidebar-nav:focus-within span.nav-hover {
|
||||
display: initial;
|
||||
color: rgb(72, 118, 255);
|
||||
}
|
||||
@@ -509,6 +513,16 @@ class Coll extends LitElement
|
||||
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 isSidebar = isReplay && this.showSidebar;
|
||||
|
||||
if (!isReplay){
|
||||
document.title = `Browse Contents | ReplayWeb.page`;
|
||||
}
|
||||
|
||||
if (this.collInfo && !this.collInfo.coll) {
|
||||
return html`
|
||||
<wr-loader .loadInfo="${this.loadInfo}" embed="${this.embed}"
|
||||
@@ -525,26 +543,27 @@ class Coll extends LitElement
|
||||
} else if (this.collInfo) {
|
||||
return html`
|
||||
${this.renderLocationBar()}
|
||||
|
||||
<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.renderCollTabs(isSidebar)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
${isReplay ? html`
|
||||
<wr-coll-replay
|
||||
.collInfo="${this.collInfo}"
|
||||
sourceUrl="${this.sourceUrl}"
|
||||
url="${this.tabData.url || ""}"
|
||||
ts="${this.tabData.ts || ""}"
|
||||
@coll-tab-nav="${this.onCollTabNav}" id="replay"
|
||||
@replay-loading="${(e) => this.isLoading = e.detail.loading}"
|
||||
class="${isReplay ? '' : 'is-hidden'}">
|
||||
</wr-coll-replay>
|
||||
<wr-coll-replay
|
||||
role="main"
|
||||
tabindex="-1"
|
||||
.collInfo="${this.collInfo}"
|
||||
sourceUrl="${this.sourceUrl}"
|
||||
url="${this.tabData.url || ""}"
|
||||
ts="${this.tabData.ts || ""}"
|
||||
@coll-tab-nav="${this.onCollTabNav}" id="replay"
|
||||
@replay-loading="${(e) => this.isLoading = e.detail.loading}"
|
||||
class="${isReplay ? '' : 'is-hidden'}">
|
||||
</wr-coll-replay>
|
||||
` : ``}
|
||||
</div>
|
||||
|
||||
`;
|
||||
} else {
|
||||
return html``;
|
||||
@@ -557,46 +576,49 @@ class Coll extends LitElement
|
||||
// }
|
||||
|
||||
return html`
|
||||
<div class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}">
|
||||
<nav class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}" aria-label="tabs">
|
||||
<ul>
|
||||
${isSidebar ? html`
|
||||
<li class="sidebar-nav left">
|
||||
<a @click="${this.onHideSidebar}" class="is-marginless is-size-6 is-paddingless">
|
||||
<fa-icon title="Hide" .svg="${fasAngleLeft}"></fa-icon><span>Hide</span>
|
||||
<a role="button" href="#" @click="${this.onHideSidebar}" @keyup="${clickOnSpacebarPress}" class="is-marginless is-size-6 is-paddingless">
|
||||
<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>
|
||||
</li>` : ``}
|
||||
|
||||
${this.hasStory ? html`
|
||||
<li class="${this.tabData.view === 'story' ? 'is-active' : ''}">
|
||||
<a @click="${this.onTabClick}" href="#story" class="is-size-6">
|
||||
<span class="icon"><fa-icon .svg="${fasBook}"></fa-icon></span>
|
||||
<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}" aria-hidden="true" title="Story"></fa-icon></span>
|
||||
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Story">Story</span>
|
||||
</a>
|
||||
</li>` : ``}
|
||||
|
||||
<li class="${this.tabData.view === 'pages' ? 'is-active' : ''}">
|
||||
<a @click="${this.onTabClick}" href="#pages" class="is-size-6">
|
||||
<span class="icon"><fa-icon .svg="${farPages}"></fa-icon></span>
|
||||
<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}" aria-hidden="true" title="Pages"></fa-icon></span>
|
||||
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Pages">Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="${this.tabData.view === 'resources' ? 'is-active' : ''}">
|
||||
<a @click="${this.onTabClick}" href="#resources" class="is-size-6">
|
||||
<span class="icon"><fa-icon .svg="${farResources}"></fa-icon></span>
|
||||
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="URL Resources">Page Resources</span>
|
||||
<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}" aria-hidden="true" title="URLs"></fa-icon></span>
|
||||
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="URLs">URLs</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
${isSidebar ? html`
|
||||
<li class="sidebar-nav right">
|
||||
<a @click="${this.onFullPageView}" class="is-marginless is-size-6 is-paddingless">
|
||||
<span>Expand</span><fa-icon title="Expand" .svg="${fasAngleRight}"></fa-icon>
|
||||
<a role="button" href="#" @click="${this.onFullPageView}" @keyup="${clickOnSpacebarPress}" class="is-marginless is-size-6 is-paddingless">
|
||||
<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>
|
||||
</li>` : ``}
|
||||
|
||||
</ul>
|
||||
</div>`;
|
||||
</nav>`;
|
||||
}
|
||||
|
||||
renderLocationBar() {
|
||||
@@ -609,84 +631,91 @@ class Coll extends LitElement
|
||||
const isReplay = !!this.tabData.url;
|
||||
|
||||
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">
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
<button class="button is-borderless is-hidden-mobile" @click="${this.onGoBack}">
|
||||
</a>
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
<button class="button is-borderless is-hidden-mobile" @click="${this.onGoForward}">
|
||||
</a>
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
<button id="refresh" class="button is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" @click="${this.onRefresh}">
|
||||
</a>
|
||||
<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">
|
||||
${!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>
|
||||
</button>
|
||||
<button class="button is-borderless is-hidden-touch ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}">
|
||||
</a>
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
</a>
|
||||
<form @submit="${this.onSubmit}">
|
||||
<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``}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="dropdown is-right ${this.menuActive ? 'is-active' : ''}" @click="${(e) => this.menuActive = false}">
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="menu-dropdown" role="menu">
|
||||
<div class="dropdown-menu" id="menu-dropdown">
|
||||
<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">
|
||||
<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>Full Screen</span>
|
||||
</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">
|
||||
<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>Back</span>
|
||||
</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">
|
||||
<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>Forward</span>
|
||||
</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">
|
||||
<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>Reload</span>
|
||||
</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">
|
||||
<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>Page Search</span>
|
||||
<span>Browse Contents</span>
|
||||
</a>
|
||||
<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
|
||||
</a>
|
||||
${dateStr ? html`
|
||||
@@ -697,7 +726,7 @@ class Coll extends LitElement
|
||||
${this.embed ? html`
|
||||
<hr class="dropdown-divider">
|
||||
<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> About ReplayWeb.page</span>
|
||||
<span class="menu-version">(${__VERSION__})</span>
|
||||
</a>
|
||||
@@ -708,7 +737,7 @@ class Coll extends LitElement
|
||||
|
||||
|
||||
</div>
|
||||
</div>`;
|
||||
</nav><p id="skip-replay-target" tabindex="-1" class="is-sr-only">Skipped</p>`;
|
||||
}
|
||||
|
||||
dragStart(event) {
|
||||
@@ -737,7 +766,9 @@ class Coll extends LitElement
|
||||
currList="${this.tabData.currList || 0}"
|
||||
@coll-tab-nav="${this.onCollTabNav}" id="story"
|
||||
.isSidebar="${isSidebar}"
|
||||
class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}">
|
||||
class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
|
||||
role="${isSidebar ? '' : 'main'}"
|
||||
>
|
||||
</wr-coll-story>` : ''}
|
||||
|
||||
<wr-coll-resources .collInfo="${this.collInfo}"
|
||||
@@ -747,7 +778,9 @@ class Coll extends LitElement
|
||||
.currMime="${this.tabData.currMime || ""}"
|
||||
@coll-tab-nav="${this.onCollTabNav}" id="resources"
|
||||
.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-page-view
|
||||
@@ -760,11 +793,20 @@ class Coll extends LitElement
|
||||
.url="${this.tabData.url || ""}"
|
||||
.ts="${this.tabData.ts || ""}"
|
||||
@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>
|
||||
`;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (event.key === "Esc" || event.key === "Escape") {
|
||||
event.target.value = this.url;
|
||||
@@ -782,7 +824,8 @@ class Coll extends LitElement
|
||||
}
|
||||
}
|
||||
|
||||
onFullscreenToggle() {
|
||||
onFullscreenToggle(event) {
|
||||
event.preventDefault();
|
||||
this.menuActive = false;
|
||||
if (!this.isFullscreen) {
|
||||
this.requestFullscreen();
|
||||
@@ -791,12 +834,14 @@ class Coll extends LitElement
|
||||
}
|
||||
}
|
||||
|
||||
onGoBack() {
|
||||
onGoBack(event) {
|
||||
event.preventDefault();
|
||||
this.menuActive = false;
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
onGoForward() {
|
||||
onGoForward(event) {
|
||||
event.preventDefault();
|
||||
this.menuActive = false;
|
||||
window.history.forward();
|
||||
}
|
||||
@@ -827,7 +872,7 @@ class Coll extends LitElement
|
||||
this.reauthWait = (async () => {
|
||||
const headers = event.detail.headers;
|
||||
|
||||
const resp = await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
|
||||
const resp = await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({headers})
|
||||
});
|
||||
@@ -921,4 +966,4 @@ class Coll extends LitElement
|
||||
|
||||
customElements.define("wr-coll", Coll);
|
||||
|
||||
export { Coll };
|
||||
export { Coll };
|
||||
|
||||
+9
-6
@@ -113,10 +113,10 @@ class Embed extends LitElement
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has("url") ||
|
||||
if (changedProperties.has("url") ||
|
||||
changedProperties.has("ts") ||
|
||||
changedProperties.has("query") ||
|
||||
changedProperties.has("view") ||
|
||||
changedProperties.has("view") ||
|
||||
changedProperties.has("source")) {
|
||||
|
||||
this.embed = this.embed || "default";
|
||||
@@ -128,7 +128,7 @@ class Embed extends LitElement
|
||||
basePageUrl: window.location.href.split("#")[0],
|
||||
embed: this.embed,
|
||||
}).toString();
|
||||
|
||||
|
||||
this.hashString = new URLSearchParams({
|
||||
url: this.url,
|
||||
ts: this.ts,
|
||||
@@ -168,13 +168,16 @@ class Embed extends LitElement
|
||||
render() {
|
||||
return 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``}
|
||||
|
||||
${this.noSW ? html`
|
||||
<section class="full-width">
|
||||
<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 class="error">
|
||||
Sorry, ReplayWeb.page won't work in this browser as Service Workers are not supported.
|
||||
@@ -214,4 +217,4 @@ async function main() {
|
||||
}
|
||||
|
||||
|
||||
main();
|
||||
main();
|
||||
|
||||
+12
-1
@@ -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;
|
||||
|
||||
|
||||
// ===========================================================================
|
||||
// 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
|
||||
{
|
||||
@@ -117,4 +128,4 @@ class AnimLogo extends FaIcon
|
||||
customElements.define("fa-icon", FaIcon);
|
||||
customElements.define("wr-anim-logo", AnimLogo);
|
||||
|
||||
export { wrapCss, IS_APP, rwpLogo, FaIcon, AnimLogo };
|
||||
export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo };
|
||||
|
||||
+15
-4
@@ -23,6 +23,7 @@ class PageEntry extends LitElement
|
||||
this.iconValid = false;
|
||||
this.index = 0;
|
||||
this.isCurrent = false;
|
||||
this.isSidebar = false;
|
||||
|
||||
this.timestamp = "";
|
||||
this.date = null;
|
||||
@@ -40,6 +41,7 @@ class PageEntry extends LitElement
|
||||
iconValid: { type: Boolean },
|
||||
index: { type: Number },
|
||||
isCurrent: { type: Boolean },
|
||||
isSidebar: { type: Boolean },
|
||||
timestamp: { type: String },
|
||||
date: { type: Object }
|
||||
}
|
||||
@@ -75,6 +77,15 @@ class PageEntry extends LitElement
|
||||
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 {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
@@ -189,7 +200,7 @@ class PageEntry extends LitElement
|
||||
<div>${date ? date.toLocaleTimeString() : ""}</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<article class="media">
|
||||
<div class="media">
|
||||
<figure class="media-left">
|
||||
<p class="">
|
||||
${this.iconValid ? html`
|
||||
@@ -198,7 +209,7 @@ class PageEntry extends LitElement
|
||||
</p>
|
||||
</figure>
|
||||
<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>` : ``}
|
||||
<p class="is-size-6 has-text-weight-bold has-text-link text">
|
||||
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
|
||||
@@ -215,10 +226,10 @@ class PageEntry extends LitElement
|
||||
<div class="media-right" style="margin-right: 2em">
|
||||
${prettyBytes(p.size)}
|
||||
</div>` : ``}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
${this.editable ? html`
|
||||
${!this.deleting ? html`
|
||||
<button @click="${this.onSendDeletePage}" class="delete"></button>` : html`
|
||||
|
||||
+62
-70
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import { LitElement, html, css, unsafeCSS } from 'lit-element';
|
||||
import { wrapCss } from './misc';
|
||||
import { wrapCss, clickOnSpacebarPress } from './misc';
|
||||
import ndjson from 'fetch-ndjson';
|
||||
|
||||
import FlexSearch from 'flexsearch';
|
||||
@@ -30,7 +30,7 @@ class Pages extends LitElement
|
||||
this.active = false;
|
||||
this.editable = false;
|
||||
this.changeNeeded = false;
|
||||
|
||||
|
||||
this.selectedPages = new Set();
|
||||
|
||||
this.menuActive = false;
|
||||
@@ -265,6 +265,10 @@ class Pages extends LitElement
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
div[role="main"], #contents div[role="complementary"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main.columns {
|
||||
width: 100%;
|
||||
justify-self: stretch;
|
||||
@@ -343,13 +347,12 @@ class Pages extends LitElement
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.index-bar-menu {
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
${Pages.sidebarStyles()}
|
||||
}
|
||||
@@ -382,7 +385,7 @@ class Pages extends LitElement
|
||||
padding-bottom: 1.0em;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
|
||||
.current {
|
||||
/*background-color: rgb(207, 243, 255);*/
|
||||
}
|
||||
@@ -467,64 +470,52 @@ class Pages extends LitElement
|
||||
const currList = this.currList;
|
||||
|
||||
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">
|
||||
${this.isSidebar ? html `<h3 class="is-sr-only">Search and Filter Pages</h3>` : ``}
|
||||
<div class="field flex-auto">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
${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 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">
|
||||
<div class="column index-bar is-one-fifth is-hidden-mobile">
|
||||
<div class="index-bar-title">${this.collInfo.title}</div>
|
||||
${this.editable ? html`
|
||||
<div class="index-bar-actions">
|
||||
${this.renderDownloadMenu()}
|
||||
</div>` : ``}
|
||||
|
||||
<span class="num-results">${this.formatResults()}</span>
|
||||
|
||||
${this.editable ? html`
|
||||
<div class="index-bar-actions">
|
||||
${this.renderDownloadMenu()}
|
||||
</div>` : ``}
|
||||
|
||||
${this.collInfo.lists.length ? html`
|
||||
<p id="filter-label" class="menu-label">Filter By List:</p>
|
||||
<aside class="index-bar-menu menu">
|
||||
<ul class="menu-list">
|
||||
${this.collInfo.lists.length ? html`
|
||||
<p id="filter-label" class="menu-label">Filter By List:</p>
|
||||
<div class="index-bar-menu menu">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a href="#list-0" data-list="0" class="${currList === 0 ? 'is-active' : ''}"
|
||||
@click=${this.onSelectList}><i>All Pages</i></a>
|
||||
</li>
|
||||
${this.collInfo.lists.map(list => html`
|
||||
<li>
|
||||
<a href="#list-0" data-list="0" class="${currList === 0 ? 'is-active' : ''}"
|
||||
@click=${this.onSelectList}><i>All Pages</i></a>
|
||||
</li>
|
||||
${this.collInfo.lists.map(list => html`
|
||||
<li>
|
||||
<a @click=${this.onSelectList} href="#list-${list.id}"
|
||||
data-list="${list.id}"
|
||||
class="${currList === list.id ? 'is-active' : ''}">${list.title}</a>
|
||||
</li>`)}
|
||||
</ul>
|
||||
</aside>
|
||||
` : ``}
|
||||
<a @click=${this.onSelectList} href="#list-${list.id}"
|
||||
data-list="${list.id}"
|
||||
class="${currList === list.id ? 'is-active' : ''}">${list.title}</a>
|
||||
</li>`)}
|
||||
</ul>
|
||||
</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>`;
|
||||
}
|
||||
|
||||
@@ -532,26 +523,26 @@ class Pages extends LitElement
|
||||
return html`
|
||||
<div class="dropdown ${this.menuActive ? 'is-active' : ''}">
|
||||
<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 class="icon is-small">
|
||||
<fa-icon .svg="${fasAngleDown}"/>
|
||||
<fa-icon .svg="${fasAngleDown} aria-hidden="true"></fa-icon>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||
<div class="dropdown-menu" id="dropdown-menu">
|
||||
<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)
|
||||
</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
|
||||
</a>
|
||||
<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)
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
@@ -570,16 +561,16 @@ class Pages extends LitElement
|
||||
|
||||
<div class="header columns is-hidden-mobile">
|
||||
${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 @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="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="title" class="column is-6 pagetitle ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<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"
|
||||
.defaultKey="${this.sortKey ? this.sortKey : (this.query ? '' : 'ts')}"
|
||||
.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">
|
||||
${this.renderPageHeader()}
|
||||
</div>
|
||||
<div class="scroller" @scroll="${this.onScroll}">
|
||||
<ul class="scroller" @scroll="${this.onScroll}">
|
||||
${this.sortedPages.length ? html`
|
||||
${this.sortedPages.map((p, i) => {
|
||||
const isCurrPage = this.isCurrPage(p);
|
||||
|
||||
return html`
|
||||
<div class="content ${isCurrPage ? 'current' : ''}">
|
||||
<li class="content ${isCurrPage ? 'current' : ''}">
|
||||
<wr-page-entry
|
||||
.index="${this.query || this.isSidebar ? i + 1 : 0}"
|
||||
.editable="${this.editable}"
|
||||
.selected="${this.selectedPages.has(p.id)}"
|
||||
.isCurrent="${isCurrPage}"
|
||||
.isSidebar="${this.isSidebar}"
|
||||
.page="${p}"
|
||||
@sel-page="${this.onSelectToggle}"
|
||||
@delete-page="${this.onDeletePage}"
|
||||
@@ -638,8 +630,8 @@ class Pages extends LitElement
|
||||
class="${this.isSidebar ? 'sidebar' : ''}"
|
||||
>
|
||||
</wr-page-entry>
|
||||
</div>` })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
|
||||
</div>
|
||||
</li>` })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -740,7 +732,7 @@ class Pages extends LitElement
|
||||
|
||||
getNoResultsMessage() {
|
||||
if (!this.collInfo || !this.collInfo.pages.length) {
|
||||
return html`No Pages defined this archive. Check out <a href="#view=resources">Page Resources</a> 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) {
|
||||
@@ -751,8 +743,8 @@ class Pages extends LitElement
|
||||
return "Searching...";
|
||||
}
|
||||
|
||||
if (!this.query) {
|
||||
return "No Pages Found. Try changing the search query.";
|
||||
if (this.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";
|
||||
|
||||
+21
-12
@@ -57,13 +57,13 @@ class Replay extends LitElement
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has("sourceUrl") || changedProperties.has("collInfo")) {
|
||||
this.isAuthable = (this.sourceUrl.startsWith("googledrive://") &&
|
||||
this.isAuthable = (this.sourceUrl.startsWith("googledrive://") &&
|
||||
this.collInfo && this.collInfo.onDemand);
|
||||
|
||||
this.reauthWait = null;
|
||||
}
|
||||
|
||||
if (this.url &&
|
||||
if (this.url &&
|
||||
((this.replayUrl != this.url) || (this.replayTS != this.ts)) &&
|
||||
(changedProperties.has("url") || changedProperties.has("ts"))) {
|
||||
|
||||
@@ -81,7 +81,7 @@ class Replay extends LitElement
|
||||
url: this.replayUrl,
|
||||
ts: this.replayTS,
|
||||
};
|
||||
|
||||
|
||||
this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {replaceLoc: true, data}}));
|
||||
}
|
||||
}
|
||||
@@ -112,13 +112,17 @@ class Replay extends LitElement
|
||||
this.title = event.data.title;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.title){
|
||||
document.title = `Replay of ${this.title} | ReplayWeb.page`;
|
||||
}
|
||||
}
|
||||
|
||||
waitForLoad() {
|
||||
this.setLoading();
|
||||
this._loadPoll = window.setInterval(() => {
|
||||
const iframe = this.renderRoot.querySelector("iframe");
|
||||
if (!iframe || !iframe.contentDocument || !iframe.contentWindow ||
|
||||
if (!iframe || !iframe.contentDocument || !iframe.contentWindow ||
|
||||
(iframe.contentDocument.readyState === "complete" && !iframe.contentWindow._WBWombat)) {
|
||||
this.clearLoading(iframe && iframe.contentWindow);
|
||||
}
|
||||
@@ -177,7 +181,7 @@ class Replay extends LitElement
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
nav.intro-panel.panel {
|
||||
div.intro-panel.panel {
|
||||
min-width: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -187,19 +191,23 @@ class Replay extends LitElement
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = `Replay of ${this.title ? `${this.title}:` :``} ${this.url}`;
|
||||
|
||||
return html`
|
||||
|
||||
<h1 id="replay-heading" class="is-sr-only">${title}</h1>
|
||||
|
||||
${this.iframeUrl ? html`
|
||||
<iframe @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen
|
||||
src="${this.iframeUrl}"></iframe>
|
||||
src="${this.iframeUrl}" title="${title}"></iframe>
|
||||
` : html`
|
||||
<nav class="panel intro-panel">
|
||||
<div class="panel intro-panel">
|
||||
<p class="panel-heading">Replay Web Page</p>
|
||||
<div class="panel-block">
|
||||
<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>
|
||||
</nav>
|
||||
</div>
|
||||
`}
|
||||
|
||||
${this.isAuthable ? html`
|
||||
@@ -207,15 +215,16 @@ class Replay extends LitElement
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<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>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ``}
|
||||
`;
|
||||
@@ -224,4 +233,4 @@ class Replay extends LitElement
|
||||
|
||||
customElements.define("wr-coll-replay", Replay);
|
||||
|
||||
export { Replay };
|
||||
export { Replay };
|
||||
|
||||
+3
-2
@@ -144,11 +144,12 @@ class Sorter extends LitElement
|
||||
</div>
|
||||
<button @click=${(e) => this.sortDesc = !this.sortDesc} class="button is-small">
|
||||
<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>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("wr-sorter", Sorter);
|
||||
|
||||
export { Sorter };
|
||||
export { Sorter };
|
||||
|
||||
+3
-1
@@ -245,7 +245,9 @@ class Story extends LitElement
|
||||
const currListNum = this.currList;
|
||||
|
||||
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="column sidebar is-one-fifth">
|
||||
<aside class="menu">
|
||||
|
||||
+41
-25
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -47,6 +47,7 @@ class URLResources extends LitElement
|
||||
constructor() {
|
||||
super();
|
||||
this.collInfo = null;
|
||||
this.isSidebar = false;
|
||||
|
||||
this.currMime = "";
|
||||
this.query = "";
|
||||
@@ -69,6 +70,7 @@ class URLResources extends LitElement
|
||||
static get properties() {
|
||||
return {
|
||||
collInfo: { type: Object },
|
||||
isSidebar: { type: Boolean },
|
||||
currMime: { type: String },
|
||||
query: { type: String },
|
||||
urlSearchType: { type: String },
|
||||
@@ -95,7 +97,7 @@ class URLResources extends LitElement
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has("query") ||
|
||||
if (changedProperties.has("query") ||
|
||||
changedProperties.has("urlSearchType") ||
|
||||
changedProperties.has("currMime")) {
|
||||
|
||||
@@ -257,7 +259,13 @@ class URLResources extends LitElement
|
||||
}
|
||||
.columns {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
thead {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
table th:not([align]) {
|
||||
text-align: left;
|
||||
}
|
||||
.result {
|
||||
border-bottom: 1px #dbdbdb solid;
|
||||
min-height: fit-content;
|
||||
@@ -266,6 +274,8 @@ class URLResources extends LitElement
|
||||
border-bottom: 2px #dbdbdb solid;
|
||||
margin-right: 16px;
|
||||
min-height: fit-content;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.results-head a {
|
||||
color: black;
|
||||
@@ -298,6 +308,9 @@ class URLResources extends LitElement
|
||||
|
||||
render() {
|
||||
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="level-left 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}"> Contains</label>
|
||||
<label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === 'prefix'}" @click="${this.onClickUrlType}"> Prefix</label>
|
||||
<label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === 'exact'}" @click="${this.onClickUrlType}"> 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 class="sort-header is-hidden-tablet">
|
||||
<wr-sorter id="urls"
|
||||
defaultKey="url"
|
||||
defaultKey="${this.sortKey ? this.sortKey : "url"}"
|
||||
?defaultDesc="${this.sortDesc !== null ? this.sortDesc : false}"
|
||||
.sortKeys="${URLResources.sortKeys}"
|
||||
@@ -340,28 +352,32 @@ class URLResources extends LitElement
|
||||
@sort-changed="${this.onSortChanged}">
|
||||
</wr-sorter>
|
||||
</div>
|
||||
|
||||
<div class="all-results">
|
||||
<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}">
|
||||
${this.sortedResults.length ?
|
||||
<div role="heading" aria-level="${this.isSidebar ? "3": "2"}" id="results-heading" class="is-sr-only">Results</div>
|
||||
|
||||
<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.map((result) => html`
|
||||
<div 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>
|
||||
<div class="column col-ts is-2"><p class="minihead is-hidden-tablet">Date</p>${new Date(result.date).toLocaleString()}</div>
|
||||
<div class="column col-mime is-3"><p class="minihead is-hidden-tablet">Mime Type</p>${result.mime}</div>
|
||||
<div class="column col-status is-1"><p class="minihead is-hidden-tablet">Status</p>${result.status}</div>
|
||||
</div>
|
||||
`) : html`<div class="section"><i>No Results Found.</i></div>`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
<tr class="columns result">
|
||||
<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>
|
||||
<td class="column col-ts is-2"><p class="minihead is-hidden-tablet">Date</p>${new Date(result.date).toLocaleString()}</td>
|
||||
<td class="column col-mime is-3"><p class="minihead is-hidden-tablet">Mime Type</p>${result.mime}</td>
|
||||
<td class="column col-status is-1"><p class="minihead is-hidden-tablet">Status</p>${result.status}</td>
|
||||
</tr>
|
||||
`) : html`<tr class="section"><td colspan="4"><i>No Results Found.</i></td></tr>`}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
onSort(event) {
|
||||
|
||||
Reference in New Issue
Block a user