1.4.0 Release (#45)

* customizations for chooser, coll-index and appmain to support usage in archiveweb.page
- chooser: support panel/message view
- move apiPrefix to misc
- make coll-index extensible, overridable styles, filtering

* api changes: make configurable in coll-index, now using '/w/' instead of '/wabac/', '/c/' prefix for coll apis

* lint pass!

* bulma: update to 0.9.2, fix delete spinner with latest bulma

* ui fixes:
- show 'Loading Archives...' on initial load until first api response succeeds
- fix typo in oncontrollerchange not actually resolving the promise!

* embed sandbox: add other allow options, except top-nav
- first api load: keep loading message until first successful api load
- don't reset hash on purge to keep current page
- sw config: fix typo with latest wabac.js
- embed: add 'noSandbox' option to avoid adding sandbox to iframe, needed to show PDFs, add more allow rules (all except top-navigation)

* ui improvements:
- refresh: ensure location refresh from hitting enter in location bar always resets url
- pageentry doubleclick: ensure double-clicking on page in sidebar always triggers reload, even if current page
- pageentry typo fix: fix missing var causing selection error (fixes webrecorder/archivewebpage#25)
- lint: don't include 'event' as global
- use latest wabac.js 2.7.0-beta.1

* add CHANGES for 1.4.0, add 'noSandbox' to embedding options
- electron app: fix path to 'w/'
- bump electron to 11.4.3

* docs: autoload current version from package.json
- chooser: support .json files for multiwacz (experimental)
- update CHANGES.md
- add missing .eslintrc.js file

* bump version to 1.4.0
This commit is contained in:
Ilya Kreymer
2021-04-25 16:45:40 -07:00
committed by GitHub
parent 36ae1aefda
commit 0ef8f955db
29 changed files with 832 additions and 663 deletions
+34 -30
View File
@@ -1,11 +1,11 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress } from "./misc";
import { registerSW } from './pageutils';
import { registerSW } from "./pageutils";
import fasHelp from '@fortawesome/fontawesome-free/svgs/solid/question-circle.svg';
import fasArrowLeft from '@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg';
import fasArrowRight from '@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg';
import fasHelp from "@fortawesome/fontawesome-free/svgs/solid/question-circle.svg";
import fasArrowLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg";
import fasArrowRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg";
// ===========================================================================
@@ -16,7 +16,7 @@ class ReplayWebApp extends LitElement
this.sourceUrl = null;
this.collTitle = null;
this.showAbout = false;
this.pageParams = {};
this.pageParams = new URLSearchParams();
this.inited = false;
this.navMenuShown = false;
@@ -25,6 +25,9 @@ class ReplayWebApp extends LitElement
this.pageTitle = "";
this.pageReplay = false;
this.loadInfo = null;
// eslint-disable-next-line no-undef
registerSW(__SW_NAME__);
this.safariKeyframes();
@@ -52,7 +55,7 @@ class ReplayWebApp extends LitElement
pageTitle: { type: String },
pageReplay: { type: Boolean },
source: { type: String }
}
};
}
static get styles() {
@@ -168,30 +171,30 @@ class ReplayWebApp extends LitElement
<span class="no-wrap is-size-6">&nbsp;&nbsp;/&nbsp;
${this.pageReplay ? html`<i>${this.pageTitle}</i>` : this.pageTitle}
</span>
` : ``}
` : ""}
`: html`
<span class="navbar-item wr-logo-item">
<fa-icon id="wrlogo" size="1.0rem" .svg=${this.mainLogo} aria-hidden="true"></fa-icon>
</span>
`}
<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}">
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>
${!this.sourceUrl ? html`
<div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}">
<div class="navbar-menu ${this.navMenuShown ? "is-active" : ""}">
<div class="navbar-start">
${IS_APP ? html`
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}" @keyup="${clickOnSpacebarPress}">
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${() => window.history.back()}" @keyup="${clickOnSpacebarPress}">
<fa-icon size="1.0rem" .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Back</span>
</a>
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${() => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
<fa-icon size="1.0rem" .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Forward</span>
</a>
` : ``}
` : ""}
</div>
${!this.embed ? html`
<div class="navbar-end">
@@ -211,8 +214,8 @@ class ReplayWebApp extends LitElement
-- NB: the About modal is currently inaccessible to people using keyboards or screen readers.
-- Should all the JS and infrastructure for accessible modals be added, or should About be a normal page?
-->
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showAbout = true} }"class="navbar-item is-size-6">About
</a>`
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showAbout = true;} }"class="navbar-item is-size-6">About
</a>`;
}
renderColl() {
@@ -225,7 +228,7 @@ class ReplayWebApp extends LitElement
@replay-favicons=${this.onFavIcons}
@update-title=${this.onTitle}
@coll-loaded=${this.onCollLoaded}
@about-show=${(e) => this.showAbout = true}></wr-coll>`;
@about-show=${() => this.showAbout = true}></wr-coll>`;
}
renderHomeIndex() {
@@ -233,7 +236,7 @@ class ReplayWebApp extends LitElement
<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>`;
}
@@ -244,17 +247,17 @@ class ReplayWebApp extends LitElement
}
return html`
${!this.embed || this.embed === "full" ? this.renderNavBar() : ''}
${!this.embed || this.embed === "full" ? this.renderNavBar() : ""}
${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()}
${this.showAbout ? this.renderAbout() : ``}
${this.showAbout ? this.renderAbout() : ""}
`;
}
firstUpdated() {
this.initRoute();
window.addEventListener("popstate", (event) => {
window.addEventListener("popstate", () => {
this.initRoute();
});
}
@@ -266,7 +269,7 @@ class ReplayWebApp 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) {
@@ -274,7 +277,7 @@ class ReplayWebApp extends LitElement
}
for (const icon of event.detail.icons) {
const link = document.createElement('link');
const link = document.createElement("link");
link.rel = icon.rel;
link.href = icon.href;
head.appendChild(link);
@@ -285,7 +288,7 @@ class ReplayWebApp extends LitElement
// 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()
this.renderRoot.querySelector("#skip-main-target").focus();
}
onNavMenu(event) {
@@ -348,7 +351,7 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("config")) {
if (!this.loadInfo) {
this.loadInfo = {}
this.loadInfo = {};
}
try {
this.loadInfo.extraConfig = JSON.parse(this.pageParams.get("config"));
@@ -359,7 +362,7 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("basePageUrl")) {
if (!this.loadInfo) {
this.loadInfo = {extraConfig: {}}
this.loadInfo = {extraConfig: {}};
}
if (!this.loadInfo.extraConfig) {
this.loadInfo.extraConfig = {};
@@ -369,18 +372,19 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("customColl")) {
if (!this.loadInfo) {
this.loadInfo = {}
this.loadInfo = {};
}
this.loadInfo.customColl = this.pageParams.get("customColl");
}
if (IS_APP && this.sourceUrl.startsWith("file://")) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("filename", this.sourceUrl.slice("file://".length));
this.loadInfo = {
sourceUrl: this.sourceUrl,
loadUrl: url.href,
}
};
}
}
@@ -456,7 +460,7 @@ class ReplayWebApp extends LitElement
<div class="modal-background" @click="${this.onAboutClose}"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">About ReplayWeb.page ${IS_APP ? 'App' : ''}</p>
<p class="modal-card-title">About ReplayWeb.page ${IS_APP ? "App" : ""}</p>
<button class="delete" aria-label="close" @click="${this.onAboutClose}"></button>
</header>
<section class="modal-card-body">
@@ -465,7 +469,7 @@ class ReplayWebApp extends LitElement
<div style="display: flex">
<div class="has-text-centered" style="width: 220px">
<wr-anim-logo class="logo" size="48px"></wr-anim-logo>
<div style="font-size: smaller; margin-bottom: 1em">${IS_APP ? 'App' : ''} v${__VERSION__}</div>
<div style="font-size: smaller; margin-bottom: 1em">${IS_APP ? "App" : ""} v${VERSION}</div>
</div>
${IS_APP ? html`
+39 -22
View File
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit-element';
import { IS_APP, wrapCss } from './misc';
import { LitElement, html, css } from "lit-element";
import { IS_APP, wrapCss } from "./misc";
import fasUpload from '@fortawesome/fontawesome-free/svgs/solid/upload.svg';
import fasUpload from "@fortawesome/fontawesome-free/svgs/solid/upload.svg";
// ===========================================================================
@@ -14,12 +14,18 @@ class Chooser extends LitElement
this.file = null;
this.hasNativeFS = !!window.showOpenFilePicker && !IS_APP;
this.newFullImport = false;
this.noHead = false;
}
static get properties() {
return {
fileDisplayName: { type: String }
}
fileDisplayName: { type: String },
newFullImport: { type: Boolean },
noHead: { type: Boolean }
};
}
onChooseFile(event) {
@@ -40,16 +46,17 @@ class Chooser extends LitElement
const options = {
types: [
{
description: 'WARC, WACZ, HAR and WBN Files',
description: "WARC, WACZ, HAR and WBN Files",
accept: {
'application/warc': ['.warc', '.gz'],
'application/har': ['.har'],
'application/wacz': ['.wacz'],
'application/wbn': ['.wbn']
"application/warc": [".warc", ".gz"],
"application/har": [".har"],
"application/wacz": [".wacz"],
"application/wbn": [".wbn"],
"application/json": [".json"],
}
}
]
}
};
const [fileHandle] = await window.showOpenFilePicker(options);
this.fileHandle = fileHandle;
@@ -71,12 +78,13 @@ class Chooser extends LitElement
loadInfo.isFile = true;
// file.path only available in electron app
if (this.file.path) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("filename", this.file.path);
loadInfo.loadUrl = url.href;
loadInfo.noCache = true;
} else if (this.fileHandle) {
loadInfo.loadUrl = this.fileDisplayName + "-" + this.randomId();
loadInfo.loadUrl = this.fileDisplayName;
loadInfo.extra = {fileHandle: this.fileHandle};
loadInfo.noCache = false;
} else {
@@ -88,6 +96,8 @@ class Chooser extends LitElement
loadInfo.name = this.fileDisplayName;
}
loadInfo.newFullImport = this.newFullImport;
this.dispatchEvent(new CustomEvent("load-start", {detail: loadInfo}));
return false;
@@ -119,11 +129,18 @@ class Chooser extends LitElement
}
.panel-heading {
background-color: #cff3ff;
}
.message-header {
background-color: #cff3ff;
color: black;
}
.heading-size {
font-size: 0.85rem;
}
form {
flex-grow: 1;
flex-shrink: 0;
margin-bottom: 0;
}
p.control.is-expanded {
width: min-content;
@@ -149,20 +166,20 @@ class Chooser extends LitElement
}
}
`);
};
}
render() {
return html`
<section class="section less-padding">
<div class="panel">
<h4 class="panel-heading">Load Web Archive</h4>
<div class="extra-padding panel-block file has-name">
<section class="section ${this.noHead ? "is-paddingless" : "less-padding"}">
<div class="${this.noHead ? "" : "panel"}">
<div class="${this.noHead ? "is-hidden" : "panel-heading"} heading-size">${this.newFullImport ? "Import Existing" : "Load"} Web Archive</div>
<div class="${this.noHead ? "" : "panel-body extra-padding"} file has-name ${this.noHead ? "is-small" : ""}">
<form class="is-flex" @submit="${this.onStartLoad}">
<label class="file-label">
${!this.hasNativeFS ? html`
<input class="file-input"
@click="${(e) => e.currentTarget.value = null}"
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ``}
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ""}
<span class="file-cta" @click="${this.onChooseNativeFile}">
<span class="file-icon">
<fa-icon size="0.9em" .svg=${fasUpload} aria-hidden="true"></fa-icon>
@@ -175,16 +192,16 @@ class Chooser extends LitElement
<div class="field has-addons">
<p class="control is-expanded">
<input class="file-name input" type="text"
<input class="file-name input ${this.noHead ? "is-small" : ""}" type="text"
name="filename" id="filename"
pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn))|(googledrive:\/\/.+)"
pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn|json))|(googledrive:\/\/.+)"
.value="${this.fileDisplayName}"
@input="${this.onInput}"
autocomplete="off"
placeholder="Enter a URL or click 'Choose File' to select a WARC, WACZ, HAR or WBN archive source">
placeholder="${this.newFullImport ? "Click 'Choose File' to select a local archive to import" : "Enter a URL or click 'Choose File' to select a WARC, WACZ, HAR or WBN archive source"}">
</p>
<div class="control">
<button type="submit" class="button is-hidden-mobile is-primary">Load</button>
<button type="submit" class="button is-hidden-mobile is-primary ${this.noHead ? "is-small" : ""}">${this.newFullImport ? "Import" : "Load"}</button>
</div>
</div>
+72 -41
View File
@@ -1,14 +1,14 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, apiPrefix } from "./misc";
import prettyBytes from 'pretty-bytes';
import prettyBytes from "pretty-bytes";
import fasCopy from '@fortawesome/fontawesome-free/svgs/regular/copy.svg';
import fasCopy from "@fortawesome/fontawesome-free/svgs/regular/copy.svg";
import fasArrowUp from '@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg';
import fasArrowDown from '@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg';
import fasArrowUp from "@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg";
import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg';
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
// ===========================================================================
@@ -19,7 +19,7 @@ class CollIndex extends LitElement
this.colls = [];
this.filteredColls = [];
this.sortedColls = [];
this.sortedColls = null;
this.query = "";
@@ -29,21 +29,24 @@ class CollIndex extends LitElement
this.dateName = "Date Loaded";
this.headerName = "Loaded Archives";
this.typeFilter = "";
this.indexParams = "";
}
get sortKeys() {
return [
{key: "title",
name: "Title"},
name: "Title"},
{key: "sourceUrl",
name: "Source"},
name: "Source"},
{key: "ctime",
name: this.dateName},
name: this.dateName},
{key: "size",
name: "Total Size"}
name: "Total Size"}
];
}
@@ -62,8 +65,8 @@ class CollIndex extends LitElement
_deleting: { type: Object },
dateName: { type: String },
headerName: { type: String }
}
headerName: { type: String },
};
}
firstUpdated() {
@@ -90,25 +93,31 @@ class CollIndex extends LitElement
for (const coll of this.colls) {
if (coll.sourceUrl.indexOf(this.query) >= 0 ||
coll.filename.indexOf(this.query) >= 0 ||
(coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) ||
(coll.title && coll.title.indexOf(this.query) >= 0)) {
this.filteredColls.push(coll);
this.filteredColls.push(coll);
}
}
}
async loadColls() {
const resp = await fetch("./wabac/api/index");
const resp = await fetch(`${apiPrefix}/coll-index?${this.indexParams}`);
try {
if (resp.status !== 200) {
throw new Error("Invalid API Response");
}
const json = await resp.json();
this.colls = json.colls.map((coll) => {
coll.title = coll.title || coll.filename;
return coll;
});
} catch (e) {
// likely no sw registered yet
}
this._deleting = {};
this._deleting = {};
this.sortedColls = [];
} catch (e) {
// likely no sw registered yet, or waiting for new sw to register
}
}
async onDeleteColl(event) {
@@ -130,7 +139,7 @@ class CollIndex extends LitElement
this._deleting[coll.sourceUrl] = true;
this.requestUpdate();
const resp = await fetch(`./wabac/api/${coll.id}`, {method: 'DELETE'});
const resp = await fetch(`${apiPrefix}/c/${coll.id}`, {method: "DELETE"});
if (resp.status === 200) {
const json = await resp.json();
this.colls = json.colls;
@@ -139,7 +148,11 @@ class CollIndex extends LitElement
}
static get styles() {
return wrapCss(css`
return wrapCss(CollIndex.compStyles);
}
static get compStyles() {
return css`
:host {
overflow-y: auto;
min-width: 0;
@@ -170,6 +183,11 @@ class CollIndex extends LitElement
fa-icon {
vertical-align: middle;
}
.panel-color {
background-color: rgb(210, 249, 214);
}
.copy {
color: black;
margin: 0px;
@@ -207,7 +225,8 @@ class CollIndex extends LitElement
.coll-block {
position: relative;
}
.delete {
.delete-button {
width: 32px;
position: absolute;
top: 10px;
right: 10px;
@@ -229,31 +248,38 @@ class CollIndex extends LitElement
height: 269px;
margin-top: -269px;
}
`);
`;
}
renderHeader() {
return html`<h2 class="panel-heading panel-color"><span>${this.headerName}</span></h2>`;
}
renderSearchHeader() {
return "";
}
render() {
const hasHeader = this.childElementCount > 0;
return html`
<header class="${this.hideHeader ? 'closed' : ''}">
<header class="${this.hideHeader ? "closed" : ""}">
<slot name="header"></slot>
</header>
<section class="section no-top-padding">
<div class="sort-header is-small">
${hasHeader ? html`
<button @click=${(e) => this.hideHeader = !this.hideHeader} class="collapse button is-small">
<button @click=${() => 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 class="is-sr-only">Header</span></span>
</button>` : ``}
<span>${this.hideHeader ? "Show " : "Hide"} <span class="is-sr-only">Header</span></span>
</button>` : ""}
</div>
<div class="panel is-light">
<h2 class="panel-heading"><span>${this.headerName}</span>
</h2>
<div class="panel">
${this.renderHeader()}
${this.colls.length ? html`
<div class="panel-block sort-header is-small">
<div class="control has-icons-left">
${this.renderSearchHeader()}
<div class="control has-icons-left has-addons">
<input type="text" class="input is-small" @input="${(e) => this.query = e.currentTarget.value}" .value="${this.query}" type="text"
placeholder="Search by Archive Title or Source">
<span class="icon is-left is-small"><fa-icon .svg="${fasSearch}"/></span>
@@ -268,13 +294,13 @@ class CollIndex extends LitElement
</div>
<div class="coll-list">
${this.sortedColls.map((coll, i) => html`
${this.sortedColls && this.sortedColls.map((coll, i) => html`
<div class="coll-block panel-block">
${this.renderCollInfo(coll)}
${!this._deleting[coll.sourceUrl] ? html`
<button class="delete" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
<button class="delete delete-button" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
` : html`
<span class="button delete is-loading is-static">Deleting</span`}
<span class="button delete-button is-loading is-static">Deleting</span`}
</div>
`)}
</div>
@@ -282,7 +308,8 @@ class CollIndex extends LitElement
` : html`
<div class="panel-block extra-padding">
${this.renderEmpty()}
${this.sortedColls === null ? html`<i>Loading Archives...</i>` :
this.renderEmpty()}
</div>
`}
</div>
@@ -314,11 +341,15 @@ class CollInfo extends LitElement
coll: { type: Object },
detailed: { type: Boolean },
canDelete: { type: Boolean }
}
};
}
static get styles() {
return wrapCss(css`
return wrapCss(CollInfo.compStyles);
}
static get compStyles() {
return css`
.columns {
width: 100%;
}
@@ -369,7 +400,7 @@ class CollInfo extends LitElement
font-size: 10px;
font-weight: bold;
}
`);
`;
}
render() {
@@ -396,7 +427,7 @@ class CollInfo extends LitElement
<span class="source-text"><p class="minihead">Source</p>${coll.sourceUrl}&nbsp;</span>
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a>
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`
<p><i>(${coll.filename})</i></p>` : ''}
<p><i>(${coll.filename})</i></p>` : ""}
</div>
${detailed ? html`
<div class="column"><p class="minihead">Filename</p>${coll.filename}</div>` : html``}
+94 -84
View File
@@ -1,28 +1,28 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress, apiPrefix, replayPrefix } from "./misc";
import { sourceToId, tsToDate, getPageDateTS } from './pageutils';
import { sourceToId, tsToDate, getPageDateTS } from "./pageutils";
import fasBook from '@fortawesome/fontawesome-free/svgs/solid/book.svg';
import fasBook from "@fortawesome/fontawesome-free/svgs/solid/book.svg";
import farListAlt from '@fortawesome/fontawesome-free/svgs/regular/list-alt.svg';
import farResources from '@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg';
import farPages from '@fortawesome/fontawesome-free/svgs/regular/file-image.svg';
import fasInfoIcon from '@fortawesome/fontawesome-free/svgs/solid/info-circle.svg';
import fasSync from '@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg';
import farListAlt from "@fortawesome/fontawesome-free/svgs/regular/list-alt.svg";
import farResources from "@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg";
import farPages from "@fortawesome/fontawesome-free/svgs/regular/file-image.svg";
import fasInfoIcon from "@fortawesome/fontawesome-free/svgs/solid/info-circle.svg";
import fasSync from "@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg";
import fasRefresh from '@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg';
import fasFullscreen from '@fortawesome/fontawesome-free/svgs/solid/desktop.svg';
import fasUnfullscreen from '@fortawesome/fontawesome-free/svgs/solid/compress-arrows-alt.svg';
import fasRefresh from "@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg";
import fasFullscreen from "@fortawesome/fontawesome-free/svgs/solid/desktop.svg";
import fasUnfullscreen from "@fortawesome/fontawesome-free/svgs/solid/compress-arrows-alt.svg";
import fasLeft from '@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg';
import fasRight from '@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg';
import fasMenuV from '@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg';
import fasLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg";
import fasRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg";
import fasMenuV from "@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg";
import fasAngleLeft from '@fortawesome/fontawesome-free/svgs/solid/angle-left.svg';
import fasAngleRight from '@fortawesome/fontawesome-free/svgs/solid/angle-right.svg';
import fasAngleLeft from "@fortawesome/fontawesome-free/svgs/solid/angle-left.svg";
import fasAngleRight from "@fortawesome/fontawesome-free/svgs/solid/angle-right.svg";
import Split from 'split.js';
import Split from "split.js";
const RWP_SCHEME = "search://";
@@ -37,11 +37,6 @@ class Coll extends LitElement
this.inited = false;
this.isLoading = false;
this.baseApiPrefix = "./wabac/api";
this.baseReplayPrefix = "./wabac";
this.apiPrefix = "";
this.replayPrefix = "";
this.coll = "";
this.collInfo = null;
@@ -61,7 +56,7 @@ class Coll extends LitElement
"story": "Story",
"resources": "URLs",
"info": "Archive Info",
}
};
this.menuActive = false;
@@ -69,7 +64,7 @@ class Coll extends LitElement
this.editable = false;
this.showSidebar = localStorage.getItem(`pages:showSidebar`) === "1";
this.showSidebar = localStorage.getItem("pages:showSidebar") === "1";
this.splitter = null;
this.isVisible = true;
@@ -112,19 +107,19 @@ class Coll extends LitElement
appName: { type: String },
appLogo: { type: String },
}
};
}
firstUpdated() {
this.inited = true;
window.addEventListener("hashchange", (event) => this.onHashChange(event));
this.addEventListener("fullscreenchange", (event) => {
this.addEventListener("fullscreenchange", () => {
this.isFullscreen = !!document.fullscreenElement;
});
if (this.embed) {
this.observer = new IntersectionObserver((entries, observer) => {
this.observer = new IntersectionObserver((entries/*, observer*/) => {
this.isVisible = entries[0].isIntersecting;
});
@@ -182,14 +177,14 @@ class Coll extends LitElement
}
}
if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, '*');
window.parent.postMessage(this.tabData, "*");
}
}
this._locUpdateNeeded = false;
}
if (changedProperties.has("showSidebar")) {
if (!this.embed) {
localStorage.setItem(`pages:showSidebar`, this.showSidebar ? "1" : "0");
localStorage.setItem("pages:showSidebar", this.showSidebar ? "1" : "0");
}
}
@@ -218,14 +213,16 @@ class Coll extends LitElement
onDragEnd() {
replay.setDisablePointer(false);
}
}
};
this.splitter = Split([contents, replay], opts);
}
} else if (this.splitter) {
try {
this.splitter.destroy();
} catch (e) {}
} catch (e) {
// ignore, remove splitter
}
this.splitter = null;
}
}
@@ -246,10 +243,10 @@ class Coll extends LitElement
this.coll = coll;
const apiPrefix = this.baseApiPrefix + "/" + coll;
const replayPrefix = this.baseReplayPrefix + "/" + coll;
const collApiPrefix = apiPrefix + "/c/" + coll;
const collReplayPrefix = replayPrefix + "/" + coll;
const resp = await fetch(apiPrefix + "?all=1");
const resp = await fetch(collApiPrefix + "?all=1");
if (resp.status != 200) {
this.collInfo = {};
@@ -258,7 +255,12 @@ class Coll extends LitElement
const json = await resp.json();
this.collInfo = {apiPrefix, replayPrefix, coll, ...json};
this.collInfo = {
apiPrefix: collApiPrefix,
replayPrefix: collReplayPrefix,
coll,
...json
};
if (!this.collInfo.title) {
this.collInfo.title = this.collInfo.filename;
@@ -281,6 +283,9 @@ class Coll extends LitElement
onCollLoaded(event) {
this.doUpdateInfo();
this.loadInfo = null;
if (event.detail.sourceUrl) {
this.sourceUrl = event.detail.sourceUrl;
}
this.dispatchEvent(new CustomEvent("coll-loaded", {detail: {
sourceUrl: this.sourceUrl,
collInfo: this.collInfo,
@@ -295,7 +300,7 @@ class Coll extends LitElement
this.collInfo = {...this.collInfo, ...event.detail};
}
onHashChange(event) {
onHashChange() {
const hash = window.location.hash;
if (hash && hash !== this._locationHash) {
this.tabData = Object.fromEntries(new URLSearchParams(hash.slice(1)).entries());
@@ -337,6 +342,11 @@ class Coll extends LitElement
}
onCollTabNav(event) {
if (event.detail.reload) {
this.onRefresh(null, true);
return;
}
if (event.target.id === this.tabData.view || event.target.id === "replay" && this.tabData.url) {
this.updateTabData(event.detail.data, event.detail.replaceLoc, false);
} else if (this.showSidebar && this.tabData.url) {
@@ -344,7 +354,7 @@ class Coll extends LitElement
}
}
updateTabData(data, replaceLoc = false, merge = false) {
updateTabData(data, replaceLoc = false/*, merge = false*/) {
this.tabData = {...this.tabData, ...data};
if (this.tabData.url) {
this.url = this.tabData.url || "";
@@ -622,7 +632,7 @@ class Coll extends LitElement
return html`
${this.renderLocationBar()}
<div id="tabContents">
<div 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)}
${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``}
@@ -641,7 +651,7 @@ class Coll extends LitElement
@replay-favicons="${this.onFavIcons}"
>
</wr-coll-replay>
` : ``}
` : ""}
</div>
`;
} else {
@@ -655,7 +665,7 @@ class Coll extends LitElement
// }
return html`
<nav class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}" aria-label="tabs">
<nav class="main tabs is-centered ${isSidebar ? "sidebar" : ""}" aria-label="tabs">
<ul>
${isSidebar ? html`
<li class="sidebar-nav left">
@@ -664,34 +674,34 @@ class Coll extends LitElement
<span class="nav-hover" aria-hidden="true">Hide</span>
<span class="is-sr-only">Hide Sidebar</span>
</a>
</li>` : ``}
</li>` : ""}
${this.hasStory ? html`
<li class="${this.tabData.view === 'story' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#story" class="is-size-6" aria-label="Story" aria-current="${this.tabData.view === 'story' ? 'location' : ''}">
<li class="${this.tabData.view === "story" ? "is-active" : ""}">
<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>
<span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="Story">Story</span>
</a>
</li>` : ``}
</li>` : ""}
<li class="${this.tabData.view === 'pages' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#pages" class="is-size-6" aria-label="Pages" aria-current="${this.tabData.view === 'pages' ? 'location' : ''}">
<li class="${this.tabData.view === "pages" ? "is-active" : ""}">
<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>
<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" aria-label="URLs" aria-current="${this.tabData.view === 'resources' ? 'location' : ''}">
<li class="${this.tabData.view === "resources" ? "is-active" : ""}">
<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>
<span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="URLs">URLs</span>
</a>
</li>
<li class="${this.tabData.view === 'info' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#info" class="is-size-6" aria-label="Archive Info" aria-current="${this.tabData.view === 'info' ? 'location' : ''}">
<li class="${this.tabData.view === "info" ? "is-active" : ""}">
<a @click="${this.onTabClick}" href="#info" class="is-size-6" aria-label="Archive Info" aria-current="${this.tabData.view === "info" ? "location" : ""}">
<span class="icon"><fa-icon .svg="${fasInfoIcon}" aria-hidden="true" title="Archive Info"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Archive Info">Info</span>
<span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="Archive Info">Info</span>
</a>
</li>
@@ -702,7 +712,7 @@ class Coll extends LitElement
<span class="is-sr-only">Expand Sidebar to Full View</span>
<fa-icon title="Expand" .svg="${fasAngleRight}" aria-hidden="true"></fa-icon>
</a>
</li>` : ``}
</li>` : ""}
</ul>
</nav>`;
}
@@ -740,15 +750,15 @@ class Coll extends LitElement
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon>
</span>
</a>
<a href="#" role="button" class="button narrow is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" id="refresh" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless ${this.isLoading ? "is-loading" : "is-hidden-mobile"}" id="refresh" @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" aria-hidden="true" .svg="${fasRefresh}"></fa-icon>
` : ``}
` : ""}
</span>
</a>
<a href="#" role="button" class="button narrow is-borderless is-hidden-mobile ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless is-hidden-mobile ${!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" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
@@ -756,7 +766,7 @@ class Coll extends LitElement
</a>
${this.renderExtraToolbar(false)}
<form @submit="${this.onSubmit}">
<div class="control is-expanded ${showFavIcon ? 'has-icons-left' : ''}">
<div class="control is-expanded ${showFavIcon ? "has-icons-left" : ""}">
<input id="url" class="input" type="text" @keydown="${this.onKeyDown}" @blur="${this.onLostFocus}" .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``}
${showFavIcon ? html`
@@ -766,7 +776,7 @@ class Coll extends LitElement
</div>
</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="${() => this.menuActive = false}">
<div class="dropdown-trigger">
<button class="button is-borderless" aria-haspopup="true" aria-controls="menu-dropdown" aria-expanded="${this.menuActive}" @click="${this.onMenu}"
aria-label="more replay controls">
@@ -801,7 +811,7 @@ class Coll extends LitElement
</span>
<span>Reload</span>
</a>
<a href="#" role="button" class="dropdown-item is-hidden-tablet ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}">
<a href="#" role="button" class="dropdown-item is-hidden-tablet ${!isReplay ? "grey-disabled" : ""}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
</span>
@@ -820,12 +830,12 @@ class Coll extends LitElement
<hr class="dropdown-divider is-hidden-desktop">
<div class="dropdown-item info is-hidden-desktop">
<span class="menu-head">Capture Date</span>${dateStr}
</div>` : ``}
</div>` : ""}
<hr class="dropdown-divider">
<a href="#" role="button" class="dropdown-item" @click="${this.onAbout}">
<fa-icon class="menu-logo" size="1.0rem" aria-hidden="true" .svg=${this.appLogo}></fa-icon>
<span>&nbsp;About ${this.appName}</span>
<span class="menu-version">(${__VERSION__})</span>
<span class="menu-version">(${VERSION})</span>
</a>
</div>
</div>
@@ -861,15 +871,15 @@ class Coll extends LitElement
</div>`;
}
renderExtraToolbar(isDropdown = false) {
return '';
renderExtraToolbar(/*isDropdown = false*/) {
return "";
}
renderCollTabs(isSidebar) {
const isStory = this.hasStory && this.tabData.view === 'story';
const isPages = this.tabData.view === 'pages';
const isResources = this.tabData.view === 'resources';
const isInfo = this.tabData.view === 'info';
const isStory = this.hasStory && this.tabData.view === "story";
const isPages = this.tabData.view === "pages";
const isResources = this.tabData.view === "resources";
const isInfo = this.tabData.view === "info";
return html`
@@ -881,10 +891,10 @@ class Coll extends LitElement
currList="${this.tabData.currList || 0}"
@coll-tab-nav="${this.onCollTabNav}" id="story"
.isSidebar="${isSidebar}"
class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
class="${isStory ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? "" : "main"}"
>
</wr-coll-story>` : ''}
</wr-coll-story>` : ""}
${isResources ? html`
<wr-coll-resources .collInfo="${this.collInfo}"
@@ -894,10 +904,10 @@ 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' : ''}"
role="${isSidebar ? '' : 'main'}"
class="is-paddingless ${isResources ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? "" : "main"}"
>
</wr-coll-resources>` : ``}
</wr-coll-resources>` : ""}
${isPages ? html`
<wr-page-view
@@ -911,10 +921,10 @@ class Coll extends LitElement
.ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="pages"
@coll-update="${this.onCollUpdate}"
class="${isPages ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
class="${isPages ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? "" : "main"}"
>
</wr-page-view>` : ``}
</wr-page-view>` : ""}
`;
}
@@ -922,7 +932,7 @@ class Coll extends LitElement
// 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()
this.renderRoot.querySelector("#skip-replay-target").focus();
}
onKeyDown(event) {
@@ -1015,7 +1025,7 @@ class Coll extends LitElement
const deleteURL = this.collInfo.apiPrefix + (reload ? "?reload=1" : "");
const resp = await fetch(deleteURL, {
method: 'DELETE',
method: "DELETE",
});
if (resp.status !== 200) {
@@ -1027,7 +1037,7 @@ class Coll extends LitElement
return;
}
window.location.hash = "";
//window.location.hash = "";
window.location.reload();
}
@@ -1099,7 +1109,7 @@ class Coll extends LitElement
return q.toString();
}
onRefresh(event) {
onRefresh(event, replayOnly = false) {
if (event) {
event.preventDefault();
}
@@ -1111,7 +1121,7 @@ class Coll extends LitElement
if (replay) {
replay.refresh();
}
} else {
} else if (!replayOnly) {
window.location.reload();
}
}
+4 -2
View File
@@ -1,5 +1,7 @@
import { ElectronReplayApp } from './electron-replay-app';
import path from 'path';
/*eslint-env node */
import { ElectronReplayApp } from "./electron-replay-app";
import path from "path";
// ============================================================================
+5 -5
View File
@@ -1,10 +1,10 @@
"use strict";
/*eslint-env node */
import { CollectionLoader } from '@webrecorder/wabac/src/loaders';
import { CollectionLoader } from "@webrecorder/wabac/src/loaders";
const { ipcRenderer, contextBridge } = require('electron');
const { ipcRenderer, contextBridge } = require("electron");
contextBridge.exposeInMainWorld('electron', {'IS_APP': true});
contextBridge.exposeInMainWorld("electron", {"IS_APP": true});
const dbs = {};
@@ -43,7 +43,7 @@ async function getResponse(event, request, coll, ts, channel) {
ipcRenderer.send(channel, result.status, headers, buffer);
}
ipcRenderer.on('getresponse', getResponse);
ipcRenderer.on("getresponse", getResponse);
export { getDB, getColl, loader };
+47 -44
View File
@@ -1,29 +1,31 @@
import fetch from 'node-fetch';
import { Headers } from 'node-fetch';
/*eslint-env node */
import {app, session, BrowserWindow, ipcMain, screen, shell } from 'electron';
import fetch from "node-fetch";
import { Headers } from "node-fetch";
import path from 'path';
import fs from 'fs';
import {app, session, BrowserWindow, ipcMain, screen, shell } from "electron";
import { ArchiveResponse, Rewriter } from '@webrecorder/wabac/src/rewrite';
import { IPFSClient } from '@webrecorder/wabac/src/ipfs';
import path from "path";
import fs from "fs";
import { PassThrough, Readable } from 'stream';
import { ArchiveResponse, Rewriter } from "@webrecorder/wabac/src/rewrite";
import { IPFSClient } from "@webrecorder/wabac/src/ipfs";
import { PassThrough, Readable } from "stream";
import { autoUpdater } from "electron-updater";
import log from "electron-log";
import mime from 'mime-types';
import mime from "mime-types";
global.Headers = Headers;
global.fetch = fetch;
const STATIC_PREFIX = "http://localhost:5471/";
const REPLAY_PREFIX = STATIC_PREFIX + "wabac/";
const REPLAY_PREFIX = STATIC_PREFIX + "w/";
const URL_RX = /([^\/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
const URL_RX = /([^/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
let IPFS = null;
@@ -76,11 +78,11 @@ class ElectronReplayApp
get mainWindowWebPreferences() {
return {
plugins: true,
preload: path.join(__dirname, 'preload.js'),
preload: path.join(__dirname, "preload.js"),
nativeWindowOpen: true,
contextIsolation: true,
enableRemoteModule: false
}
};
}
get mainWindowUrl() {
@@ -95,7 +97,7 @@ class ElectronReplayApp
console.log("App already running, opening new window in first instance and quitting");
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
app.on("second-instance", (event, commandLine/*, workingDir*/) => {
// Just create a new window in case of second instance request
this.createMainWindow(commandLine);
});
@@ -103,43 +105,43 @@ class ElectronReplayApp
if (includePlugins) {
switch (process.platform) {
case 'win32':
this.pluginPath = path.join(this.projPath, "plugins-win", `pepflashplayer-x86${process.arch === 'x64' ? '_64' : ''}.dll`);
break;
case "win32":
this.pluginPath = path.join(this.projPath, "plugins-win", `pepflashplayer-x86${process.arch === "x64" ? "_64" : ""}.dll`);
break;
case 'darwin':
this.pluginPath = path.join(this.projPath, "plugins-mac", "PepperFlashPlayer.plugin");
break;
case "darwin":
this.pluginPath = path.join(this.projPath, "plugins-mac", "PepperFlashPlayer.plugin");
break;
case 'linux':
this.pluginPath = path.join(this.projPath, "plugins-mac", "libpepflashplayer.so");
break;
case "linux":
this.pluginPath = path.join(this.projPath, "plugins-mac", "libpepflashplayer.so");
break;
default:
console.log('platform unsupported: ' + process.platform);
break;
default:
console.log("platform unsupported: " + process.platform);
break;
}
app.commandLine.appendSwitch('ppapi-flash-path', this.pluginPath);
app.commandLine.appendSwitch("ppapi-flash-path", this.pluginPath);
}
console.log('app path', this.appPath);
console.log('dir name', __dirname);
console.log('proj path', this.projPath);
console.log("app path", this.appPath);
console.log("dir name", __dirname);
console.log("proj path", this.projPath);
if (includePlugins) {
console.log('plugin path', this.pluginPath);
console.log("plugin path", this.pluginPath);
}
console.log('app data', app.getPath('appData'));
console.log('user data', app.getPath('userData'));
console.log("app data", app.getPath("appData"));
console.log("user data", app.getPath("userData"));
if (this.profileName) {
app.setPath('userData', path.join(app.getPath('appData'), this.profileName));
app.setPath("userData", path.join(app.getPath("appData"), this.profileName));
}
app.on('will-finish-launching', () => {
app.on('open-file', (event, filePath) => {
app.on("will-finish-launching", () => {
app.on("open-file", (event, filePath) => {
this.openNextFile = filePath;
if (this.mainWindow) {
this.createMainWindow(process.argv);
@@ -147,7 +149,7 @@ class ElectronReplayApp
});
});
app.on('activate', function () {
app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
@@ -158,7 +160,7 @@ class ElectronReplayApp
app.whenReady().then(() => this.onAppReady());
// Quit when all windows are closed.
app.on('window-all-closed', function () {
app.on("window-all-closed", function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
//if (process.platform !== 'darwin')
@@ -177,8 +179,8 @@ class ElectronReplayApp
this.screenSize = screen.getPrimaryDisplay().workAreaSize;
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', async (event, navigationUrl) => {
app.on("web-contents-created", (event, contents) => {
contents.on("new-window", async (event, navigationUrl) => {
// load docs in native browser for now
if (navigationUrl === STATIC_PREFIX + "docs") {
@@ -251,6 +253,7 @@ class ElectronReplayApp
return this.notFound(request.url, callback);
}
// eslint-disable-next-line no-undef
if (request.url.startsWith(__APP_FILE_SERVE_PREFIX__)) {
const parsedUrl = new URL(request.url);
@@ -330,7 +333,7 @@ class ElectronReplayApp
notFound(url, callback) {
console.log("not found: " + url);
const data = this._bufferToStream(`Sorry, the url <b>${url}</b> could not be found in this archive.`);
callback({statusCode: 404, headers: {"Content-Type": 'text/html; charset="utf-8"'}, data});
callback({statusCode: 404, headers: {"Content-Type": "text/html; charset=\"utf-8\""}, data});
}
async resolveArchiveResponse(request, callback) {
@@ -340,7 +343,7 @@ class ElectronReplayApp
const url = request.url;
if (status === 404 && !payload) {
return notFound(url, callback);
return this.notFound(url, callback);
} else {
console.log("got response for: " + url);
}
@@ -426,7 +429,7 @@ class ElectronReplayApp
isMaximized: true,
show: false,
webPreferences: this.mainWindowWebPreferences,
}).once('ready-to-show', () => {
}).once("ready-to-show", () => {
theWindow.show();
theWindow.maximize();
});
@@ -440,7 +443,7 @@ class ElectronReplayApp
}
getOpenUrl(argv) {
argv = require('minimist')(argv.slice(process.defaultApp ? 2 : 1));
argv = require("minimist")(argv.slice(process.defaultApp ? 2 : 1));
const filename = this.openNextFile || argv.filename || argv.f || (argv._.length && argv._[0]);
this.openNextFile = null;
+17 -9
View File
@@ -1,8 +1,9 @@
import { LitElement, html, css } from 'lit-element';
import { LitElement, html, css } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined.js";
import { registerSW } from './pageutils';
import { registerSW } from "./pageutils";
import { wrapCss, rwpLogo } from './misc';
import { wrapCss, rwpLogo } from "./misc";
var scriptSrc = document.currentScript && document.currentScript.src;
@@ -14,6 +15,7 @@ class Embed extends LitElement
constructor() {
super();
this.replaybase = "./replay/";
// eslint-disable-next-line no-undef
this.swName = __SW_NAME__;
this.view = "replay";
this.ts = "";
@@ -26,6 +28,7 @@ class Embed extends LitElement
this.swInited = false;
this.embed = null;
this.reloadCount = 0;
this.noSandbox = false;
}
static get properties() {
@@ -53,7 +56,8 @@ class Embed extends LitElement
deepLink: { type: Boolean },
noSW: { type: Boolean },
}
noSandbox: { type: Boolean }
};
}
async doRegister() {
@@ -91,7 +95,7 @@ class Embed extends LitElement
if (this.deepLink) {
this.updateFromHash();
window.addEventListener("hashchange", (event) => this.updateFromHash());
window.addEventListener("hashchange", () => this.updateFromHash());
}
}
@@ -170,9 +174,13 @@ 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"
title="Replay of ${this.title ? `${this.title}:` :``} ${this.url}"></iframe>
<iframe sandbox="${ifDefined(!this.noSandbox ?
"allow-downloads allow-modals allow-orientation-lock allow-pointer-lock\
allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts\
allow-same-origin allow-forms" : undefined)}"
@load="${this.onLoad}" src="${this.replaybase}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen"
title="Replay of ${this.title ? `${this.title}:` :""} ${this.url}"></iframe>
` : html``}
@@ -187,7 +195,7 @@ Please try a different browser.\n
(Service Workers are disabled in Firefox in Private Mode. If Using Private Mode in Firefox, try regular mode).
</div>
</section>
`: ``}`;
`: ""}`;
}
onLoad(event) {
+20 -18
View File
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss } from "./misc";
import fabGoogleDrive from '@fortawesome/fontawesome-free/svgs/brands/google-drive.svg';
import fabGoogleDrive from "@fortawesome/fontawesome-free/svgs/brands/google-drive.svg";
// ===========================================================================
@@ -21,7 +21,7 @@ class GDrive extends LitElement
sourceUrl: { type: String },
error: { type: Boolean },
reauth: { type: Boolean }
}
};
}
updated(changedProperties) {
@@ -41,6 +41,7 @@ class GDrive extends LitElement
try {
const sourceUrl = this.sourceUrl;
const fileId = sourceUrl.slice("googledrive://".length);
// eslint-disable-next-line no-undef
const publicCheckUrl = `${__HELPER_PROXY__}/g/${fileId}`;
let resp = null;
@@ -87,7 +88,7 @@ class GDrive extends LitElement
onLoad() {
this.scriptLoaded = true;
this.gauth('none', (response) => {
this.gauth("none", (response) => {
if (response.error) {
if (this.state !== "implicitonly") {
this.state = "trymanual";
@@ -99,7 +100,7 @@ class GDrive extends LitElement
}
onClickAuth() {
this.gauth('select_account', (response) => {
this.gauth("select_account", (response) => {
if (!response.error) {
this.authed(response);
}
@@ -144,12 +145,12 @@ class GDrive extends LitElement
` : html`
${this.error ? html`
<div class="error has-text-danger">
<p>${this.reauth ? 'Some resources are loaded on demand from Google Drive, which requires reauthorization.' :
'Could not access this file with the current Google Drive account.'}</p>
<p>${this.reauth ? "Some resources are loaded on demand from Google Drive, which requires reauthorization." :
"Could not access this file with the current Google Drive account."}</p>
<p>If you have multiple Google Drive accounts, be sure to select the correct one.</p>
</div>
<br/>
` : ``}
` : ""}
<button class="button is-warning is-rounded" @click="${this.onClickAuth}">
<span class="icon"><fa-icon .svg="${fabGoogleDrive}"></fa-icon></span>
<span>Authorize Google Drive</span>
@@ -161,19 +162,20 @@ class GDrive extends LitElement
if (this.state === "trypublic" || this.scriptLoaded) {
return html``;
}
const script = document.createElement('script');
const script = document.createElement("script");
script.onload = (() => this.onLoad());
script.src = 'https://apis.google.com/js/platform.js';
script.src = "https://apis.google.com/js/platform.js";
return script;
}
gauth(prompt, callback) {
gapi.load('auth2', () => {
gapi.auth2.authorize({
client_id: __GDRIVE_CLIENT_ID__,
scope: "https://www.googleapis.com/auth/drive.file",
response_type: "token",
prompt
self.gapi.load("auth2", () => {
self.gapi.auth2.authorize({
// eslint-disable-next-line no-undef
client_id: __GDRIVE_CLIENT_ID__,
scope: "https://www.googleapis.com/auth/drive.file",
response_type: "token",
prompt
}, callback);
});
}
@@ -181,4 +183,4 @@ class GDrive extends LitElement
customElements.define("wr-gdrive", GDrive);
export { GDrive };
export { GDrive };
+83 -80
View File
@@ -1,9 +1,9 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, IS_APP } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, IS_APP } from "./misc";
import prettyBytes from 'pretty-bytes';
import prettyBytes from "pretty-bytes";
import { parseURLSchemeHostPath } from './pageutils';
import { parseURLSchemeHostPath } from "./pageutils";
// ===========================================================================
@@ -46,10 +46,10 @@ class Loader extends LitElement
embed: { type: String },
tryFileHandle: { type: Boolean },
errorAllowRetry: { type: Boolean }
}
};
}
firstUpdated(props) {
firstUpdated() {
this.initMessages();
//this.doLoad();
}
@@ -61,41 +61,41 @@ class Loader extends LitElement
navigator.serviceWorker.addEventListener("message", (event) => {
switch (event.data.msg_type) {
case "collProgress":
if (event.data.name === this.coll) {
this.percent = event.data.percent;
if (event.data.error) {
this.error = event.data.error;
this.state = "errored";
this.errorAllowRetry = true;
this.fileHandle = event.data.fileHandle;
if (this.error === "missing_local_file") {
this.tryFileHandle = false;
} else if (this.error === "permission_needed" && event.data.fileHandle) {
this.state = "permission_needed";
break;
}
}
if (event.data.currentSize && event.data.totalSize) {
this.currentSize = event.data.currentSize;
this.totalSize = event.data.totalSize;
case "collProgress":
if (event.data.name === this.coll) {
this.percent = event.data.percent;
if (event.data.error) {
this.error = event.data.error;
this.state = "errored";
this.errorAllowRetry = true;
this.fileHandle = event.data.fileHandle;
if (this.error === "missing_local_file") {
this.tryFileHandle = false;
} else if (this.error === "permission_needed" && event.data.fileHandle) {
this.state = "permission_needed";
break;
}
}
break;
if (event.data.currentSize && event.data.totalSize) {
this.currentSize = event.data.currentSize;
this.totalSize = event.data.totalSize;
}
}
break;
case "collAdded":
if (event.data.name === this.coll) {
if (!this.total) {
this.total = 100;
}
this.progress = this.total;
this.percent = 100;
if (this.pingInterval) {
clearInterval(this.pingInterval);
}
this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data}));
case "collAdded":
if (event.data.name === this.coll) {
if (!this.total) {
this.total = 100;
}
break;
this.progress = this.total;
this.percent = 100;
if (this.pingInterval) {
clearInterval(this.pingInterval);
}
this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data}));
}
break;
}
});
}
@@ -124,38 +124,39 @@ Please try loading this page from an HTTPS URL`;
const {scheme, host, path} = parseURLSchemeHostPath(sourceUrl);
switch (scheme) {
case "googledrive":
this.state = "googledrive";
source = await this.googledriveInit();
break;
case "googledrive":
this.state = "googledrive";
source = await this.googledriveInit();
break;
case "s3":
source = {sourceUrl,
loadUrl: `https://${host}.s3.amazonaws.com${path}`,
name: this.sourceUrl};
break;
case "s3":
source = {sourceUrl,
loadUrl: `https://${host}.s3.amazonaws.com${path}`,
name: this.sourceUrl};
break;
case "file":
if (!this.loadInfo && !this.tryFileHandle) {
this.state = "errored";
this.error = `\
case "file":
if (!this.loadInfo && !this.tryFileHandle) {
this.state = "errored";
this.error = `\
File URLs can not be entered directly or shared.
You can select a file to upload from the main page by clicking the \'Choose File...\' button.`;
this.errorAllowRetry = false;
return;
}
You can select a file to upload from the main page by clicking the 'Choose File...' button.`;
this.errorAllowRetry = false;
return;
}
source = this.loadInfo;
break;
source = this.loadInfo;
break;
case "ipfs":
if (IS_APP) {
const url = new URL(__APP_FILE_SERVE_PREFIX__);
const hash = sourceUrl.split("#", 1)[0];
url.searchParams.set("ipfs", hash.slice("ipfs://".length));
source = {sourceUrl, loadUrl: url.href};
}
break;
case "ipfs":
if (IS_APP) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__);
const hash = sourceUrl.split("#", 1)[0];
url.searchParams.set("ipfs", hash.slice("ipfs://".length));
source = {sourceUrl, loadUrl: url.href};
}
break;
}
} catch (e) {
console.log(e);
@@ -165,6 +166,8 @@ You can select a file to upload from the main page by clicking the \'Choose File
source = {sourceUrl};
}
source.newFullImport = (this.loadInfo && this.loadInfo.newFullImport);
this.state = "started";
const msg = {"msg_type": "addColl", "name": this.coll, skipExisting: true, file: source};
@@ -175,7 +178,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
if (!navigator.serviceWorker.controller) {
await new Promise((resolve) => {
navigator.serviceWorker.addEventListener("controllerchange", () => resolve);
navigator.serviceWorker.addEventListener("controllerchange", () => resolve());
});
}
@@ -269,7 +272,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
${!this.embed ? html`
<div class="level">
<p class="level-item">Loading&nbsp;<b>${this.sourceUrl}</b>...</p>
</div>` : ``}
</div>` : ""}
<div class="level">
<div class="level-item has-text-centered">
${this.renderContent()}
@@ -281,11 +284,11 @@ You can select a file to upload from the main page by clicking the \'Choose File
renderContent() {
switch (this.state) {
case "googledrive":
return html`<wr-gdrive .sourceUrl=${this.sourceUrl} @load-ready=${this.onLoadReady}/>`
case "googledrive":
return html`<wr-gdrive .sourceUrl=${this.sourceUrl} @load-ready=${this.onLoadReady}/>`;
case "started":
return html`
case "started":
return html`
<div class="progress-div">
<progress id="progress" class="progress is-primary is-large"
value="${this.percent}" max="100"></progress>
@@ -294,31 +297,31 @@ You can select a file to upload from the main page by clicking the \'Choose File
<p class="loaded-prog">Loaded <b>${prettyBytes(this.currentSize)}</b> of <b>${prettyBytes(this.totalSize)}</b></p>` : html``}
${!this.embed ? html`
<button @click="${this.onCancel}" class="button is-danger">Cancel</button>` : ``}
<button @click="${this.onCancel}" class="button is-danger">Cancel</button>` : ""}
</div>`;
case "errored":
return html`
case "errored":
return html`
<div class="has-text-left">
<div class="error has-text-danger">${this.error}</div>
<div>
${this.errorAllowRetry ? html`
<a class="button is-warning" @click=${(e) => window.parent.location.reload()}>Try Again</a>` : ``}
<a class="button is-warning" @click=${() => window.parent.location.reload()}>Try Again</a>` : ""}
${this.embed ? html`` : html`
<a href="/" class="button is-warning">Back</a>`}
</div>`;
case "permission_needed":
return html`
case "permission_needed":
return html`
<div class="has-text-left">
<div class="">Permission is needed to reload the archive file. (Click <i>Cancel</i> to cancel loading this archive.)</div>
<button @click="${this.onAskPermission}" class="button is-primary">Show Permission</button>
<a href="/" class="button is-danger">Cancel</a>
</div>`;
case "waiting":
default:
return html`<progress class="progress is-primary is-large" style="max-width: 400px"/>`;
case "waiting":
default:
return html`<progress class="progress is-primary is-large" style="max-width: 400px"/>`;
}
}
@@ -333,4 +336,4 @@ You can select a file to upload from the main page by clicking the \'Choose File
customElements.define("wr-loader", Loader);
export { Loader };
export { Loader };
+15 -10
View File
@@ -1,12 +1,15 @@
"use strict";
import { LitElement, html, css, unsafeCSS } from 'lit-element';
import { unsafeSVG } from 'lit-html/directives/unsafe-svg';
import { styleMap } from 'lit-html/directives/style-map';
import { LitElement, html, css, unsafeCSS } from "lit-element";
import { unsafeSVG } from "lit-html/directives/unsafe-svg";
import { styleMap } from "lit-html/directives/style-map";
import allCssRaw from '../assets/main.scss';
import allCssRaw from "../assets/main.scss";
import rwpLogo from '../assets/logo.svg';
import rwpLogo from "../assets/logo.svg";
const apiPrefix = "./w/api";
const replayPrefix = "./w";
// ===========================================================================
@@ -15,8 +18,10 @@ function wrapCss(custom) {
return [allCss, 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;
// eslint-disable-next-line no-undef
const VERSION = __VERSION__;
// ===========================================================================
// Buttons are expected to respond to both enter/return and spacebar.
@@ -45,7 +50,7 @@ class FaIcon extends LitElement
size: { type: String },
width: { type: String },
height: { type: String }
}
};
}
static get styles() {
@@ -137,7 +142,7 @@ class WrModal extends LitElement
return {
title: { type: String },
bgClass: { type: String }
}
};
}
static get styles() {
@@ -178,5 +183,5 @@ customElements.define("wr-anim-logo", AnimLogo);
customElements.define("wr-modal", WrModal);
export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo, WrModal,
LitElement, html, css, unsafeCSS, unsafeSVG };
export { wrapCss, IS_APP, VERSION, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo, WrModal,
LitElement, html, css, unsafeCSS, unsafeSVG, apiPrefix, replayPrefix };
+27 -23
View File
@@ -1,12 +1,12 @@
import prettyBytes from 'pretty-bytes';
import prettyBytes from "pretty-bytes";
import { LitElement, html, css, unsafeCSS } from 'lit-element';
import { LitElement, html, css, unsafeCSS } from "lit-element";
import "keyword-mark-element/lib/keyword-mark.js";
import { getReplayLink } from './pageutils';
import { getReplayLink } from "./pageutils";
import { wrapCss } from './misc';
import { wrapCss } from "./misc";
// ===========================================================================
@@ -39,7 +39,7 @@ class PageEntry extends LitElement
index: { type: Number },
isCurrent: { type: Boolean },
isSidebar: { type: Boolean },
}
};
}
static get styles() {
@@ -95,7 +95,7 @@ class PageEntry extends LitElement
align-self: center;
}
.delete {
.delete-button {
position: absolute;
top: 8px;
right: 8px;
@@ -190,12 +190,12 @@ class PageEntry extends LitElement
<label class="checkbox">
<input @change=${this.onSendSelToggle} type="checkbox" .checked="${this.selected}">
</label>
</div>` : ``}
</div>` : ""}
<div class="columns">
${this.index ? html`
<div class="column col-index is-1 is-size-7">${this.index}.</div>
` : ``}
` : ""}
<div class="column col-date is-2">
<div>${date ? date.toLocaleDateString() : ""}</div>
<div>${date ? date.toLocaleTimeString() : ""}</div>
@@ -205,13 +205,13 @@ class PageEntry extends LitElement
<figure class="media-left">
<p class="">
${this.iconValid ? html`
<img class="favicon" @error="${(e) => this.iconValid = false}" src="${this.replayPrefix}/${this.page.timestamp}id_/${p.favIconUrl}"/>` : html`
<img class="favicon" @error="${() => this.iconValid = false}" src="${this.replayPrefix}/${this.page.timestamp}id_/${p.favIconUrl}"/>` : html`
<span class="favicon"></span>`}
</p>
</figure>
<div class="media-content ${this.isCurrent ? 'current' : ''}">
<div class="media-content ${this.isCurrent ? "current" : ""}">
<div role="heading" aria-level="${this.isSidebar ? "4": "3"}">
<a @click="${this.onReplay}" href="${getReplayLink("pages", this.page.url, this.page.timestamp)}">
<a @dblclick="${this.onReload}" @click="${this.onReplay}" href="${getReplayLink("pages", this.page.url, this.page.timestamp)}">
<p class="is-size-6 has-text-weight-bold has-text-link text">
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
</p>
@@ -226,16 +226,16 @@ class PageEntry extends LitElement
${hasSize ? html`
<div class="media-right" style="margin-right: 2em">
${prettyBytes(p.size)}
</div>` : ``}
</div>` : ""}
</div>
</div>
</div>
${editable ? html`
${!this.deleting ? html`
<button @click="${this.onSendDeletePage}" class="delete"></button>` : html`
<button class="button delete is-loading is-static"></button>
`}` : ''}
<button @click="${this.onSendDeletePage}" class="delete delete-button"></button>` : html`
<button class="button is-loading delete-button is-static"></button>
`}` : ""}
`;
}
@@ -268,7 +268,7 @@ class PageEntry extends LitElement
if (!this.query || !this.page.text) {
this.textSnippet = null;
this.requestUpdate('textSnippet', oldVal);
this.requestUpdate("textSnippet", oldVal);
return;
}
@@ -285,7 +285,7 @@ class PageEntry extends LitElement
if (inx < 0) {
this.textSnippet = null;
this.requestUpdate('textSnippet', oldVal);
this.requestUpdate("textSnippet", oldVal);
return;
}
@@ -305,25 +305,29 @@ class PageEntry extends LitElement
this.textSnippet = "..." + textContent.slice(inx, lastInx) + "...";
}
this.requestUpdate('textSnippet', oldVal);
this.requestUpdate("textSnippet", oldVal);
}
onReplay(event) {
onReplay(event, reload = false) {
event.preventDefault();
const data = {
url: this.page.url,
ts: this.page.timestamp,
};
this.sendChangeEvent(data);
this.sendChangeEvent(data, reload);
return false;
}
sendChangeEvent(data) {
this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data}}));
onReload(event) {
return this.onReplay(event, true);
}
onSendDeletePage(event) {
sendChangeEvent(data, reload) {
this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data, reload}}));
}
onSendDeletePage() {
const page = this.page;
this.dispatchEvent(new CustomEvent("delete-page", {detail: {page}}));
}
+47 -42
View File
@@ -1,18 +1,18 @@
"use strict";
import { LitElement, html, css, unsafeCSS } from 'lit-element';
import { wrapCss, clickOnSpacebarPress } from './misc';
import ndjson from 'fetch-ndjson';
import { LitElement, html, css, unsafeCSS } from "lit-element";
import { wrapCss, clickOnSpacebarPress } from "./misc";
import ndjson from "fetch-ndjson";
import FlexSearch from 'flexsearch';
import prettyBytes from 'pretty-bytes';
import FlexSearch from "flexsearch";
import prettyBytes from "pretty-bytes";
import { getTS, getPageDateTS } from './pageutils';
import { getTS, getPageDateTS } from "./pageutils";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg';
import fasAngleDown from '@fortawesome/fontawesome-free/svgs/solid/angle-down.svg';
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import fasAngleDown from "@fortawesome/fontawesome-free/svgs/solid/angle-down.svg";
import fasEdit from '@fortawesome/fontawesome-free/svgs/solid/edit.svg';
import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
// ===========================================================================
@@ -99,7 +99,7 @@ class Pages extends LitElement
toDeletePages: { type: Object },
toDeletePage: { type: Object }
}
};
}
_timedUpdate() {
@@ -131,7 +131,7 @@ class Pages extends LitElement
this.sortKey = "";
this.sortDesc = false;
} else {
this.sortKey = "date"
this.sortKey = "date";
this.sortDesc = true;
}
const sorter = this.renderRoot.querySelector("wr-sorter");
@@ -143,11 +143,11 @@ class Pages extends LitElement
if (changedProperties.has("sortedPages") && this.isSidebar) {
//if (await this.updateComplete) {
const selected = this.renderRoot.querySelector(".current");
if (selected) {
const opts = {behavior: "smooth", block: "nearest", inline: "nearest"};
setTimeout(() => selected.scrollIntoView(opts), 100);
}
const selected = this.renderRoot.querySelector(".current");
if (selected) {
const opts = {behavior: "smooth", block: "nearest", inline: "nearest"};
setTimeout(() => selected.scrollIntoView(opts), 100);
}
//}
}
}
@@ -241,13 +241,13 @@ class Pages extends LitElement
let lines = [];
async function flush() {
const flush = async () => {
let curr = lines;
lines = [];
await flex.add(curr);
console.log("added " + count + " " + curr.length);
console.log(flex.info());
}
};
for await (const line of ndjson(resp.body.getReader())) {
if (!line.text) {
@@ -516,9 +516,9 @@ class Pages extends LitElement
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>` : ``}
${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' : ''}">
<div class="control has-icons-left ${this.loading ? "is-loading" : ""}">
<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}" aria-hidden="true"></fa-icon></span>
@@ -531,7 +531,7 @@ class Pages extends LitElement
${this.editable && this.editing ? html`
<form @submit="${this.onUpdateTitle}"><input id="titleEdit" class="input" value="${this.collInfo.title}" @blur="${this.onUpdateTitle}"></form>
` : html`
<div class="index-bar-title" @dblclick="${(e) => this.editing = true}">${this.collInfo.title}</div>`}
<div class="index-bar-title" @dblclick="${() => this.editing = true}">${this.collInfo.title}</div>`}
${this.editable ? html`<fa-icon class="editIcon" .svg="${fasEdit}"></fa-icon>` : html``}
@@ -540,25 +540,25 @@ class Pages extends LitElement
${this.editable ? html`
<div class="index-bar-actions">
${this.renderDownloadMenu()}
</div>` : ``}
</div>` : ""}
${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' : ''}"
<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>
class="${currList === list.id ? "is-active" : ""}">${list.title}</a>
</li>`)}
</ul>
</div>
` : ``}
` : ""}
</div>
<div class="column main-content">
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "3": "2"}">Page List</div>
@@ -571,7 +571,7 @@ class Pages extends LitElement
renderDownloadMenu() {
return html`
<div class="dropdown ${this.menuActive ? 'is-active' : ''}">
<div class="dropdown ${this.menuActive ? "is-active" : ""}">
<div class="dropdown-trigger">
<button @click="${this.onMenu}" class="button is-small" aria-haspopup="true" aria-expanded="${this.menuActive}" aria-controls="dropdown-menu">
<span>Download</span>
@@ -585,13 +585,13 @@ class Pages extends LitElement
<a role="button" href="#"
@click="${(e) => this.onDownload(e, "wacz", true)}"
@keyup="${clickOnSpacebarPress}"
class="dropdown-item ${this.selectedPages.size === 0 ? 'is-disabled' : ''}">
class="dropdown-item ${this.selectedPages.size === 0 ? "is-disabled" : ""}">
Download Selected as WACZ (Web Archive Collection)
</a>
<a role="button" href="#"
@click="${(e) => this.onDownload(e, "warc", true)}"
@keyup="${clickOnSpacebarPress}"
class="dropdown-item ${this.selectedPages.size === 0 ? 'is-disabled' : ''}">
class="dropdown-item ${this.selectedPages.size === 0 ? "is-disabled" : ""}">
Download Selected as WARC Only
</a>
<hr class="dropdown-divider">
@@ -623,10 +623,10 @@ class Pages extends LitElement
<div class="header columns is-hidden-mobile">
${this.query ? html`
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" 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 role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="date" class="column is-2 ${this.sortKey === "date" ? (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>
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="date" class="column is-2 ${this.sortKey === "date" ? (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">
@@ -638,7 +638,7 @@ class Pages extends LitElement
.data="${this.filteredPages}"
pageResults="100"
@sort-changed="${this.onSortChanged}"
class="${this.filteredPages.length ? '' : 'is-hidden'}">
class="${this.filteredPages.length ? "" : "is-hidden"}">
</wr-sorter>
</div>
`;
@@ -650,7 +650,7 @@ class Pages extends LitElement
}
return html`
<wr-modal bgClass="has-background-grey-lighter" @modal-closed="${(e) => this.toDeletePages = this.toDeletePage = null}" title="Confirm Delete">
<wr-modal bgClass="has-background-grey-lighter" @modal-closed="${() => this.toDeletePages = this.toDeletePage = null}" title="Confirm Delete">
${this.toDeletePage ? html`
<p>Are you sure you want to delete the page <b>${this.toDeletePage.title}</b>?
(Size: <b>${prettyBytes(this.toDeletePage.size)}</b>)</p>` : html`
@@ -659,7 +659,7 @@ class Pages extends LitElement
<p>This operation can not be undone.</p>
<button @click="${this.onDeletePages}"class="button is-danger">Delete</button>
<button @click="${(e) => this.toDeletePages = this.toDeletePage = null}" class="button">Cancel</button>
<button @click="${() => this.toDeletePages = this.toDeletePage = null}" class="button">Cancel</button>
</wr-modal>`;
}
@@ -688,10 +688,10 @@ class Pages extends LitElement
<ul class="scroller" @scroll="${this.onScroll}">
${this.sortedPages.length ? html`
${this.sortedPages.map((p, i) => {
const selected = this.selectedPages.has(p.id);
const selected = this.selectedPages.has(p.id);
return html`
<li class="page-entry ${selected ? 'selected' : ''}">
return html`
<li class="page-entry ${selected ? "selected" : ""}">
<wr-page-entry
.index="${this.query || this.isSidebar ? i + 1 : 0}"
.editable="${this.editable}"
@@ -704,10 +704,10 @@ class Pages extends LitElement
@delete-page="${this.onDeleteConfirm}"
replayPrefix="${this.collInfo.replayPrefix}"
query="${this.query}"
class="${this.isSidebar ? 'sidebar' : ''}"
class="${this.isSidebar ? "sidebar" : ""}"
>
</wr-page-entry>
</li>` })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
</li>`; })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
</ul>
`;
}
@@ -729,7 +729,7 @@ class Pages extends LitElement
const body = JSON.stringify({title});
fetch(`${this.collInfo.apiPrefix}/metadata`, {method: 'POST', body}).then((res) => {
fetch(`${this.collInfo.apiPrefix}/metadata`, {method: "POST", body}).then((res) => {
if (res.status === 200) {
this.dispatchEvent(new CustomEvent("coll-update", {detail: {title}}));
}
@@ -828,9 +828,14 @@ class Pages extends LitElement
}
for (const id of this.toDeletePages) {
const resp = await fetch(`${this.collInfo.apiPrefix}/page/${id}`, {method: 'DELETE'});
const resp = await fetch(`${this.collInfo.apiPrefix}/page/${id}`, {method: "DELETE"});
const json = await resp.json();
if (json.error) {
console.warn(json.error);
continue;
}
const page = pageMap[id];
if (!page) {
@@ -866,7 +871,7 @@ class Pages extends LitElement
}
if (this.updatingSearch) {
return "Initializing Search..."
return "Initializing Search...";
}
if (this.loading) {
+11 -10
View File
@@ -1,4 +1,4 @@
import { register } from 'register-service-worker';
import { register } from "register-service-worker";
// ===========================================================================
@@ -17,13 +17,13 @@ function registerSW(name = "sw.js", scope = "./") {
register(scope + name, {
registrationOptions: { scope },
registered (reg) {
console.log('Service worker is registered');
registered () {
console.log("Service worker is registered");
resolve();
},
error (error) {
console.error('Error during service worker registration:', error);
console.error("Error during service worker registration:", error);
reject();
}
});
@@ -36,6 +36,7 @@ let dbworker = null;
function initDBWorker() {
if (dbworker === null) {
// eslint-disable-next-line no-undef
dbworker = new Worker(__SW_NAME__);
}
return dbworker;
@@ -47,7 +48,7 @@ async function digestMessage(message, hashtype) {
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest(hashtype, msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join(""); // convert bytes to hex string
return hashHex;
}
@@ -70,7 +71,7 @@ function tsToDate(ts) {
ts.substring(12, 14) + "-00:00");
return new Date(datestr);
};
}
// ===========================================================================
function getPageDateTS(page) {
@@ -78,7 +79,7 @@ function getPageDateTS(page) {
try {
date = new Date(page.ts || page.date);
} catch (e) {
// leave date unchanged in case of error
}
const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : "";
@@ -88,7 +89,7 @@ function getPageDateTS(page) {
// ===========================================================================
function getTS(iso) {
return iso.replace(/[-:T]/g, '').slice(0, 14);
return iso.replace(/[-:T]/g, "").slice(0, 14);
}
@@ -104,7 +105,7 @@ function getReplayLink(view, url, ts) {
// ===========================================================================
async function sourceToId(url) {
const digest = await digestMessage(url, 'SHA-256');
const digest = await digestMessage(url, "SHA-256");
const coll = "id-" + digest.slice(0, 12);
return {url, coll};
}
@@ -144,4 +145,4 @@ function parseURLSchemeHostPath(url) {
export { digestMessage, tsToDate, getTS, getPageDateTS, getReplayLink, sourceToId, parseURLSchemeHostPath,
registerSW, initDBWorker };
registerSW, initDBWorker };
+20 -11
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, rwpLogo } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo } from "./misc";
@@ -38,7 +38,7 @@ class Replay extends LitElement
showAuth: { type: Boolean },
authFileHandle: { type: Object }
}
};
}
firstUpdated() {
@@ -57,7 +57,9 @@ class Replay extends LitElement
this.refresh();
return;
}
} catch (e) {}
} catch (e) {
console.warn(e);
}
} else {
this.authFileHandle = null;
}
@@ -147,8 +149,8 @@ class Replay extends LitElement
// google drive reauth
const headers = event.detail.headers;
const resp = await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
method: 'POST',
await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
method: "POST",
body: JSON.stringify({headers})
});
} else {
@@ -173,7 +175,7 @@ class Replay extends LitElement
const iframe = this.renderRoot.querySelector("iframe");
if (!iframe || !iframe.contentDocument || !iframe.contentWindow ||
(iframe.contentDocument.readyState === "complete" && !iframe.contentWindow._WBWombat)) {
this.clearLoading(iframe && iframe.contentWindow);
this.clearLoading(iframe && iframe.contentWindow);
}
}, 5000);
}
@@ -200,9 +202,16 @@ class Replay extends LitElement
refresh() {
const iframe = this.renderRoot.querySelector("iframe");
if (iframe) {
if (!iframe) {
return;
}
const oldIframeUrl = this.iframeUrl;
// set iframe url to expected, refresh if same url
this.doSetIframeUrl();
if (oldIframeUrl === this.iframeUrl) {
this.waitForLoad();
iframe.contentWindow.location.reload();
iframe.contentWindow.location.href = this.iframeUrl;
}
}
@@ -262,7 +271,7 @@ class Replay extends LitElement
}
render() {
const title = `Replay of ${this.title ? `${this.title}:` :``} ${this.url}`;
const title = `Replay of ${this.title ? `${this.title}:` :""} ${this.url}`;
return html`
@@ -302,7 +311,7 @@ class Replay extends LitElement
</div>
</div>
</div>
`: ``}
`: ""}
</div>
`}`;
}
+7 -7
View File
@@ -1,8 +1,8 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss } from "./misc";
import fasSortDown from '@fortawesome/fontawesome-free/svgs/solid/sort-down.svg';
import fasSortUp from '@fortawesome/fontawesome-free/svgs/solid/sort-up.svg';
import fasSortDown from "@fortawesome/fontawesome-free/svgs/solid/sort-down.svg";
import fasSortUp from "@fortawesome/fontawesome-free/svgs/solid/sort-up.svg";
// ===========================================================================
@@ -16,7 +16,7 @@ class Sorter extends LitElement
this.pageResults = 0;
this.numResults = 0;
this.sortKey = null
this.sortKey = null;
this.sortDesc = null;
}
@@ -31,7 +31,7 @@ class Sorter extends LitElement
sortKey: { type: String },
sortDesc: { type: Boolean },
}
};
}
firstUpdated() {
@@ -126,7 +126,7 @@ class Sorter extends LitElement
`)}
</select>
</div>
<button @click=${(e) => this.sortDesc = !this.sortDesc} class="button is-small">
<button @click=${() => this.sortDesc = !this.sortDesc} class="button is-small">
<span>Order:</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>
+18 -16
View File
@@ -1,12 +1,12 @@
import { LitElement, html, css, unsafeCSS } from 'lit-element';
import { wrapCss } from './misc';
import { LitElement, html, css, unsafeCSS } from "lit-element";
import { wrapCss } from "./misc";
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
import marked from 'marked';
import { unsafeHTML } from "lit-html/directives/unsafe-html";
import marked from "marked";
import { getTS, getReplayLink } from './pageutils';
import { getTS, getReplayLink } from "./pageutils";
import Split from 'split.js'
import Split from "split.js";
@@ -43,7 +43,7 @@ class Story extends LitElement
isSidebar: { type: Boolean },
splitDirection: { type: Boolean }
}
};
}
recalcSplitter(width) {
@@ -53,11 +53,11 @@ class Story extends LitElement
firstUpdated() {
this.recalcSplitter(document.documentElement.clientWidth);
this.obs = new ResizeObserver((entries, observer) => {
this.obs = new ResizeObserver((entries/*, observer*/) => {
this.recalcSplitter(entries[0].contentRect.width);
});
this.obs.observe(this)
this.obs.observe(this);
}
updated(changedProperties) {
@@ -87,7 +87,9 @@ class Story extends LitElement
if (this.splitter) {
try {
this.splitter.destroy();
} catch (e) {}
} catch (e) {
// ignore splitter destory err
}
this.splitter = null;
}
@@ -98,7 +100,7 @@ class Story extends LitElement
gutterSize: 4,
direction: this.splitDirection
}
};
this.splitter = Split([sidebar, content], opts);
}
@@ -160,7 +162,7 @@ class Story extends LitElement
width: 100% !important;
}
${Story.sidebarStyles(unsafeCSS(`:host(.sidebar)`))}
${Story.sidebarStyles(unsafeCSS(":host(.sidebar)"))}
.desc p {
margin-bottom: 1.0em;
@@ -257,13 +259,13 @@ class Story extends LitElement
<aside class="menu">
<ul class="menu-list">
<li>
<a href="#list-0" data-list="0" class="${currListNum === 0 ? 'is-active' : ''} menu-label is-size-4"
<a href="#list-0" data-list="0" class="${currListNum === 0 ? "is-active" : ""} menu-label is-size-4"
@click=${this.onClickScroll}>${this.collInfo.title}</a>
<ul class="menu-list">${this.collInfo.lists.map(list => html`
<li>
<a @click=${this.onClickScroll} href="#list-${list.id}"
data-list="${list.id}"
class="${currListNum === list.id ? 'is-active' : ''}">${list.title}</a>
class="${currListNum === list.id ? "is-active" : ""}">${list.title}</a>
</li>`)}
</ul>
</li>
@@ -273,7 +275,7 @@ class Story extends LitElement
<div @scroll=${this.onScroll} class="column main-content main-scroll">
<section id="list-0" class="desc">
<h2 class="has-text-centered title is-3">${this.collInfo.title}</h2>
${this.collInfo.desc ? unsafeHTML(marked(this.collInfo.desc)) : ''}
${this.collInfo.desc ? unsafeHTML(marked(this.collInfo.desc)) : ""}
</section>
${this.renderLists()}
</div>
@@ -283,7 +285,7 @@ class Story extends LitElement
renderLists() {
return html`
${this.collInfo.lists.map((list, i) => html`
${this.collInfo.lists.map((list) => html`
<article id="list-${list.id}">
<div class="content">
<hr/>
+3 -3
View File
@@ -1,6 +1,6 @@
import INDEX_HTML from '../index.html';
import INDEX_HTML from "../index.html";
import { SWReplay } from '@webrecorder/wabac/src/swmain';
import { SWReplay } from "@webrecorder/wabac/src/swmain";
const staticData = new Map();
@@ -12,5 +12,5 @@ staticData.set(prefix + "index.html", {type: "text/html", content: INDEX_HTML});
//const { SWReplay } = require('@webrecorder/wabac/src/swmain');
self.ipfsCustomPreload = true;
self.sw = new SWReplay(staticData);
self.sw = new SWReplay({staticData});
+15 -15
View File
@@ -1,18 +1,18 @@
import { ReplayWebApp } from './appmain';
import { Chooser } from './chooser';
import { CollIndex, CollInfo } from './coll-index';
import { Coll } from './coll';
import { Story } from './story';
import { GDrive } from './gdrive';
import { Loader } from './loader';
import { Pages } from './pages';
import { PageEntry } from './pageentry';
import { Replay } from './replay';
import { Sorter } from './sorter';
import { URLResources } from './url-resources';
import { Embed } from './embed';
import { ReplayWebApp } from "./appmain";
import { Chooser } from "./chooser";
import { CollIndex, CollInfo } from "./coll-index";
import { Coll } from "./coll";
import { Story } from "./story";
import { GDrive } from "./gdrive";
import { Loader } from "./loader";
import { Pages } from "./pages";
import { PageEntry } from "./pageentry";
import { Replay } from "./replay";
import { Sorter } from "./sorter";
import { URLResources } from "./url-resources";
import { Embed } from "./embed";
export { ReplayWebApp, Chooser, CollIndex, CollInfo, Coll, Story, GDrive,
Loader, Pages, PageEntry,
Replay, Sorter, URLResources, Embed };
Loader, Pages, PageEntry,
Replay, Sorter, URLResources, Embed };
+14 -14
View File
@@ -1,9 +1,9 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, clickOnSpacebarPress } from './misc';
import { LitElement, html, css } from "lit-element";
import { wrapCss, clickOnSpacebarPress } from "./misc";
import { getReplayLink } from './pageutils';
import { getReplayLink } from "./pageutils";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg';
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import "keyword-mark-element/lib/keyword-mark.js";
@@ -83,7 +83,7 @@ class URLResources extends LitElement
loading: { type: Boolean },
sortKey: { type: String },
sortDesc: { type: Boolean }
}
};
}
firstUpdated() {
@@ -318,7 +318,7 @@ class URLResources extends LitElement
</select>
</div>
<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" placeholder="Enter URL to Search">
<span class="icon is-left"><fa-icon .svg="${fasSearch}"/></span>
</div>
@@ -327,9 +327,9 @@ class URLResources extends LitElement
</div>
<div class="control level-right">
<div style="margin-left: 1em" class="control">
<label class="radio has-text-left"><input type="radio" name="urltype" value="contains" ?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="exact" ?checked="${this.urlSearchType === 'exact'}" @click="${this.onClickUrlType}">&nbsp;Exact</label>
<label class="radio has-text-left"><input type="radio" name="urltype" value="contains" ?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="exact" ?checked="${this.urlSearchType === "exact"}" @click="${this.onClickUrlType}">&nbsp;Exact</label>
<span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${this.filteredResults.length} Result(s)</span>
</div>
</div>
@@ -350,16 +350,16 @@ class URLResources extends LitElement
<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>
<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`
this.sortedResults.map((result) => html`
<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="${getReplayLink("resources", result.url, result.ts)}">
<keyword-mark keywords="${this.query}">${result.url}</keyword-mark>