Format src files with Prettier (#215)
Formats files in `src` with [prettier for consistent code style](https://prettier.io/docs/en/why-prettier) and adds npm script Related to https://github.com/webrecorder/replayweb.page/issues/201
This commit is contained in:
+4
-13
@@ -3,7 +3,10 @@ module.exports = {
|
|||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2018,
|
"ecmaVersion": 2018,
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
@@ -13,21 +16,9 @@ module.exports = {
|
|||||||
2,
|
2,
|
||||||
"event", "error"
|
"event", "error"
|
||||||
],
|
],
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
"linebreak-style": [
|
||||||
"error",
|
"error",
|
||||||
"unix"
|
"unix"
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything except src
|
||||||
|
*
|
||||||
|
!/src/
|
||||||
|
!src/*
|
||||||
+4
-1
@@ -34,9 +34,11 @@
|
|||||||
"electron-builder": "^23.0.3",
|
"electron-builder": "^23.0.3",
|
||||||
"electron-notarize": "^1.1.1",
|
"electron-notarize": "^1.1.1",
|
||||||
"eslint": "^8.23.1",
|
"eslint": "^8.23.1",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"http-server": "^13.0.2",
|
"http-server": "^13.0.2",
|
||||||
"mini-css-extract-plugin": "^2.3.0",
|
"mini-css-extract-plugin": "^2.3.0",
|
||||||
"node-sass": "^9.0.0",
|
"node-sass": "^9.0.0",
|
||||||
|
"prettier": "^3.0.1",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
"style-loader": "^3.2.1",
|
"style-loader": "^3.2.1",
|
||||||
@@ -67,7 +69,8 @@
|
|||||||
"dist": "yarn run build && yarn run pack",
|
"dist": "yarn run build && yarn run pack",
|
||||||
"dist-dev": "yarn run build-dev && yarn run pack",
|
"dist-dev": "yarn run build-dev && yarn run pack",
|
||||||
"release": "yarn run build && electron-builder",
|
"release": "yarn run build && electron-builder",
|
||||||
"lint": "eslint ./src/ webpack.config.js"
|
"lint": "eslint ./src/ webpack.config.js",
|
||||||
|
"format": "prettier --write ./src"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"afterSign": "build/notarize.js",
|
"afterSign": "build/notarize.js",
|
||||||
|
|||||||
+206
-76
@@ -1,15 +1,19 @@
|
|||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress } from "./misc";
|
import {
|
||||||
|
wrapCss,
|
||||||
|
rwpLogo,
|
||||||
|
IS_APP,
|
||||||
|
VERSION,
|
||||||
|
clickOnSpacebarPress,
|
||||||
|
} from "./misc";
|
||||||
|
|
||||||
import fasHelp from "@fortawesome/fontawesome-free/svgs/solid/question-circle.svg";
|
import fasHelp from "@fortawesome/fontawesome-free/svgs/solid/question-circle.svg";
|
||||||
import fasArrowLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg";
|
import fasArrowLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg";
|
||||||
import fasArrowRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg";
|
import fasArrowRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg";
|
||||||
import { SWManager } from "./swmanager";
|
import { SWManager } from "./swmanager";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class ReplayWebApp extends LitElement
|
class ReplayWebApp extends LitElement {
|
||||||
{
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
constructor(swName = __SW_NAME__) {
|
constructor(swName = __SW_NAME__) {
|
||||||
super();
|
super();
|
||||||
@@ -105,7 +109,7 @@ class ReplayWebApp extends LitElement
|
|||||||
static get appStyles() {
|
static get appStyles() {
|
||||||
return css`
|
return css`
|
||||||
#wrlogo {
|
#wrlogo {
|
||||||
max-height: 1.0rem;
|
max-height: 1rem;
|
||||||
}
|
}
|
||||||
.navbar {
|
.navbar {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
@@ -147,7 +151,7 @@ class ReplayWebApp extends LitElement
|
|||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
}
|
}
|
||||||
.tagline {
|
.tagline {
|
||||||
margin-top: 1.0rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drop-file-overlay {
|
.drop-file-overlay {
|
||||||
@@ -160,7 +164,7 @@ class ReplayWebApp extends LitElement
|
|||||||
z-index: 50;
|
z-index: 50;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
background: rgba(255,255,255,.5);
|
background: rgba(255, 255, 255, 0.5);
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +188,7 @@ class ReplayWebApp extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-menu {
|
.info-menu {
|
||||||
padding: 0 1.0em;
|
padding: 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-text {
|
.logo-text {
|
||||||
@@ -202,7 +206,6 @@ class ReplayWebApp extends LitElement
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +214,10 @@ class ReplayWebApp extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderNavBrand() {
|
renderNavBrand() {
|
||||||
return html`
|
return html` <span
|
||||||
<span id="home" class="logo-text has-text-weight-bold is-size-6 has-allcaps wide-only">
|
id="home"
|
||||||
|
class="logo-text has-text-weight-bold is-size-6 has-allcaps wide-only"
|
||||||
|
>
|
||||||
<span class="has-text-primary">replay</span>
|
<span class="has-text-primary">replay</span>
|
||||||
<span class="has-text-link">web.page</span>
|
<span class="has-text-link">web.page</span>
|
||||||
<span class="is-sr-only">Home</span>
|
<span class="is-sr-only">Home</span>
|
||||||
@@ -220,70 +225,145 @@ class ReplayWebApp extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderNavBar() {
|
renderNavBar() {
|
||||||
return html`
|
return html` <a
|
||||||
<a href="#skip-main-target" @click=${this.skipMenu} class="skip-link">Skip main navigation</a>
|
href="#skip-main-target"
|
||||||
|
@click=${this.skipMenu}
|
||||||
|
class="skip-link"
|
||||||
|
>Skip main navigation</a
|
||||||
|
>
|
||||||
<nav class="navbar has-background-info" aria-label="main">
|
<nav class="navbar has-background-info" aria-label="main">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
${!this.embed ? html`
|
${!this.embed
|
||||||
<a href="${this.homeUrl}" class="navbar-item wr-logo-item" aria-labelledby="home">
|
? html`
|
||||||
<fa-icon id="wrlogo" size="1.0rem" .svg=${this.mainLogo} aria-hidden="true"></fa-icon>
|
<a
|
||||||
|
href="${this.homeUrl}"
|
||||||
|
class="navbar-item wr-logo-item"
|
||||||
|
aria-labelledby="home"
|
||||||
|
>
|
||||||
|
<fa-icon
|
||||||
|
id="wrlogo"
|
||||||
|
size="1.0rem"
|
||||||
|
.svg=${this.mainLogo}
|
||||||
|
aria-hidden="true"
|
||||||
|
></fa-icon>
|
||||||
${this.renderNavBrand()}
|
${this.renderNavBrand()}
|
||||||
</a>
|
</a>
|
||||||
${this.collTitle ? html`
|
${this.collTitle
|
||||||
<a href="${this.collPageUrl}" class="no-wrap is-size-6 has-text-black">/ <i>${this.collTitle}</i></a>
|
? html`
|
||||||
<span class="no-wrap is-size-6"> /
|
<a
|
||||||
${this.pageReplay ? html`<i>${this.pageTitle}</i>` : this.pageTitle}
|
href="${this.collPageUrl}"
|
||||||
|
class="no-wrap is-size-6 has-text-black"
|
||||||
|
>/ <i>${this.collTitle}</i></a
|
||||||
|
>
|
||||||
|
<span class="no-wrap is-size-6"
|
||||||
|
> /
|
||||||
|
${this.pageReplay
|
||||||
|
? html`<i>${this.pageTitle}</i>`
|
||||||
|
: this.pageTitle}
|
||||||
</span>
|
</span>
|
||||||
` : ""}
|
`
|
||||||
`: html`
|
: ""}
|
||||||
|
`
|
||||||
|
: html`
|
||||||
<span class="navbar-item wr-logo-item">
|
<span class="navbar-item wr-logo-item">
|
||||||
<fa-icon id="wrlogo" size="1.0rem" .svg=${this.mainLogo} aria-hidden="true"></fa-icon>
|
<fa-icon
|
||||||
|
id="wrlogo"
|
||||||
|
size="1.0rem"
|
||||||
|
.svg=${this.mainLogo}
|
||||||
|
aria-hidden="true"
|
||||||
|
></fa-icon>
|
||||||
</span>
|
</span>
|
||||||
`}
|
`}
|
||||||
<a href="#" role="button" id="menu-button" @click="${this.onNavMenu}" @keyup="${clickOnSpacebarPress}"
|
<a
|
||||||
class="navbar-burger burger ${this.navMenuShown ? "is-active" : ""}" aria-label="main menu" aria-haspopup="true" aria-expanded="${this.navMenuShown}">
|
href="#"
|
||||||
|
role="button"
|
||||||
|
id="menu-button"
|
||||||
|
@click="${this.onNavMenu}"
|
||||||
|
@keyup="${clickOnSpacebarPress}"
|
||||||
|
class="navbar-burger burger ${this.navMenuShown ? "is-active" : ""}"
|
||||||
|
aria-label="main menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="${this.navMenuShown}"
|
||||||
|
>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
${!this.sourceUrl ? html`
|
${!this.sourceUrl
|
||||||
<div class="navbar-menu ${this.navMenuShown ? "is-active" : ""}">
|
? html` <div
|
||||||
|
class="navbar-menu ${this.navMenuShown ? "is-active" : ""}"
|
||||||
|
>
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
${IS_APP ? html`
|
${IS_APP
|
||||||
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${() => window.history.back()}" @keyup="${clickOnSpacebarPress}">
|
? html`
|
||||||
<fa-icon size="1.0rem" .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7"> Go Back</span>
|
<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"> Go Back</span>
|
||||||
</a>
|
</a>
|
||||||
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${() => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
|
<a
|
||||||
<fa-icon size="1.0rem" .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7"> Go Forward</span>
|
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"
|
||||||
|
> Go Forward</span
|
||||||
|
>
|
||||||
</a>
|
</a>
|
||||||
` : ""}
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
${!this.embed ? html`
|
${!this.embed
|
||||||
<div class="navbar-end">
|
? html` <div class="navbar-end">${this.renderNavEnd()}</div>`
|
||||||
${this.renderNavEnd()}
|
: html``}
|
||||||
</div>` : html``}
|
</div>`
|
||||||
</div>` : html``}
|
: html``}
|
||||||
</nav>
|
</nav>
|
||||||
<p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>`;
|
<p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNavEnd() {
|
renderNavEnd() {
|
||||||
return html`
|
return html` <a href="/docs" target="_blank" class="navbar-item is-size-6">
|
||||||
<a href="/docs" target="_blank" class="navbar-item is-size-6">
|
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon
|
||||||
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span> User Docs</span>
|
><span> User Docs</span>
|
||||||
</a>
|
</a>
|
||||||
<!--
|
<!--
|
||||||
-- NB: the About modal is currently inaccessible to people using keyboards or screen readers.
|
-- 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?
|
-- 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
|
||||||
|
href="?terms"
|
||||||
|
@click="${(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.showAbout = true;
|
||||||
|
}}"
|
||||||
|
class="navbar-item is-size-6"
|
||||||
|
>About
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderColl() {
|
renderColl() {
|
||||||
return html`
|
return html` <wr-coll
|
||||||
<wr-coll .loadInfo="${this.loadInfo}"
|
.loadInfo="${this.loadInfo}"
|
||||||
sourceUrl="${this.sourceUrl}"
|
sourceUrl="${this.sourceUrl}"
|
||||||
embed="${this.embed}"
|
embed="${this.embed}"
|
||||||
appName="${this.appName}"
|
appName="${this.appName}"
|
||||||
@@ -292,16 +372,29 @@ class ReplayWebApp extends LitElement
|
|||||||
@replay-favicons=${this.onFavIcons}
|
@replay-favicons=${this.onFavIcons}
|
||||||
@update-title=${this.onTitle}
|
@update-title=${this.onTitle}
|
||||||
@coll-loaded=${this.onCollLoaded}
|
@coll-loaded=${this.onCollLoaded}
|
||||||
@about-show=${() => this.showAbout = true}></wr-coll>`;
|
@about-show=${() => (this.showAbout = true)}
|
||||||
|
></wr-coll>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHomeIndex() {
|
renderHomeIndex() {
|
||||||
return html`
|
return html` <wr-coll-index>
|
||||||
<wr-coll-index>
|
${!IS_APP
|
||||||
${!IS_APP ? html`
|
? 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>
|
<p slot="header" class="tagline is-size-5 has-text-centered">
|
||||||
` : ""}
|
Explore and Replay Interactive Archived Webpages Directly in your
|
||||||
<wr-chooser slot="header" .droppedFile=${this.droppedFile} @did-drop-file="${() => this.droppedFile = null}" @load-start=${this.onStartLoad}></wr-chooser>
|
Browser.
|
||||||
|
<i
|
||||||
|
><a target="_blank" href="./docs/examples">(See Examples)</a></i
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<wr-chooser
|
||||||
|
slot="header"
|
||||||
|
.droppedFile=${this.droppedFile}
|
||||||
|
@did-drop-file="${() => (this.droppedFile = null)}"
|
||||||
|
@load-start=${this.onStartLoad}
|
||||||
|
></wr-chooser>
|
||||||
</wr-coll-index>`;
|
</wr-coll-index>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,11 +409,8 @@ class ReplayWebApp extends LitElement
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!this.embed || this.embed === "full" ? this.renderNavBar() : ""}
|
${!this.embed || this.embed === "full" ? this.renderNavBar() : ""}
|
||||||
|
|
||||||
${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()}
|
${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()}
|
||||||
|
|
||||||
${this.showAbout ? this.renderAbout() : ""}
|
${this.showAbout ? this.renderAbout() : ""}
|
||||||
|
|
||||||
${this.showFileDropOverlay ? this.renderDropFileOverlay() : ""}
|
${this.showFileDropOverlay ? this.renderDropFileOverlay() : ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -332,7 +422,12 @@ class ReplayWebApp extends LitElement
|
|||||||
const name = this.swName + (this.useRuffle ? "?ruffle=1" : "");
|
const name = this.swName + (this.useRuffle ? "?ruffle=1" : "");
|
||||||
|
|
||||||
this.swmanager = new SWManager({ name, appName: this.appName });
|
this.swmanager = new SWManager({ name, appName: this.appName });
|
||||||
this.swmanager.register().catch(() => this.swErrorMsg = this.swmanager.renderErrorReport(this.mainLogo));
|
this.swmanager
|
||||||
|
.register()
|
||||||
|
.catch(
|
||||||
|
() =>
|
||||||
|
(this.swErrorMsg = this.swmanager.renderErrorReport(this.mainLogo)),
|
||||||
|
);
|
||||||
|
|
||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("popstate", () => {
|
||||||
this.initRoute();
|
this.initRoute();
|
||||||
@@ -377,18 +472,26 @@ class ReplayWebApp extends LitElement
|
|||||||
// Since this menu can be large and obscure significant page content,
|
// Since this menu can be large and obscure significant page content,
|
||||||
// dismiss like a modal, returning keyboard focus to the
|
// dismiss like a modal, returning keyboard focus to the
|
||||||
// menu button on dismissal.
|
// menu button on dismissal.
|
||||||
document.addEventListener("click", (event) => {
|
document.addEventListener(
|
||||||
|
"click",
|
||||||
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.navMenuShown = false;
|
this.navMenuShown = false;
|
||||||
this.renderRoot.querySelector("#menu-button").focus();
|
this.renderRoot.querySelector("#menu-button").focus();
|
||||||
}, {once: true});
|
},
|
||||||
document.addEventListener("keypress", (event) => {
|
{ once: true },
|
||||||
|
);
|
||||||
|
document.addEventListener(
|
||||||
|
"keypress",
|
||||||
|
(event) => {
|
||||||
if (event.key == "Escape") {
|
if (event.key == "Escape") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.navMenuShown = false;
|
this.navMenuShown = false;
|
||||||
this.renderRoot.querySelector("#menu-button").focus();
|
this.renderRoot.querySelector("#menu-button").focus();
|
||||||
}
|
}
|
||||||
}, {once: true});
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +504,11 @@ class ReplayWebApp extends LitElement
|
|||||||
if (state) {
|
if (state) {
|
||||||
try {
|
try {
|
||||||
state = JSON.parse(state);
|
state = JSON.parse(state);
|
||||||
if ((state.ids instanceof Array) && state.userId && state.action === "open") {
|
if (
|
||||||
|
state.ids instanceof Array &&
|
||||||
|
state.userId &&
|
||||||
|
state.action === "open"
|
||||||
|
) {
|
||||||
this.pageParams.set("source", "googledrive://" + state.ids[0]);
|
this.pageParams.set("source", "googledrive://" + state.ids[0]);
|
||||||
this.pageParams.delete("state");
|
this.pageParams.delete("state");
|
||||||
window.location.search = this.pageParams.toString();
|
window.location.search = this.pageParams.toString();
|
||||||
@@ -448,7 +555,9 @@ class ReplayWebApp extends LitElement
|
|||||||
|
|
||||||
if (this.pageParams.get("baseUrlSourcePrefix")) {
|
if (this.pageParams.get("baseUrlSourcePrefix")) {
|
||||||
this.loadInfo.extraConfig = this.loadInfo.extraConfig || {};
|
this.loadInfo.extraConfig = this.loadInfo.extraConfig || {};
|
||||||
this.loadInfo.extraConfig.baseUrlSourcePrefix = this.pageParams.get("baseUrlSourcePrefix");
|
this.loadInfo.extraConfig.baseUrlSourcePrefix = this.pageParams.get(
|
||||||
|
"baseUrlSourcePrefix",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pageParams.get("basePageUrl")) {
|
if (this.pageParams.get("basePageUrl")) {
|
||||||
@@ -464,7 +573,6 @@ class ReplayWebApp extends LitElement
|
|||||||
this.loadInfo.noWebWorker = true;
|
this.loadInfo.noWebWorker = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.pageParams.get("noCache") === "1") {
|
if (this.pageParams.get("noCache") === "1") {
|
||||||
this.loadInfo.noCache = true;
|
this.loadInfo.noCache = true;
|
||||||
}
|
}
|
||||||
@@ -484,7 +592,7 @@ class ReplayWebApp extends LitElement
|
|||||||
if (IS_APP && this.sourceUrl.startsWith("file://")) {
|
if (IS_APP && this.sourceUrl.startsWith("file://")) {
|
||||||
this.loadInfo = {
|
this.loadInfo = {
|
||||||
sourceUrl: this.sourceUrl,
|
sourceUrl: this.sourceUrl,
|
||||||
loadUrl: this.sourceUrl.replace("file://", "file2://")
|
loadUrl: this.sourceUrl.replace("file://", "file2://"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,7 +609,6 @@ class ReplayWebApp extends LitElement
|
|||||||
window.location.search = this.pageParams.toString();
|
window.location.search = this.pageParams.toString();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
window.history.pushState({}, "", this.collPageUrl);
|
window.history.pushState({}, "", this.collPageUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +640,11 @@ class ReplayWebApp extends LitElement
|
|||||||
this.pageTitle = event.detail.title;
|
this.pageTitle = event.detail.title;
|
||||||
this.pageReplay = event.detail.replayTitle;
|
this.pageReplay = event.detail.replayTitle;
|
||||||
|
|
||||||
document.title = (event.detail.replayTitle ? "Archive of " : "") + this.pageTitle + " | " + this.appName;
|
document.title =
|
||||||
|
(event.detail.replayTitle ? "Archive of " : "") +
|
||||||
|
this.pageTitle +
|
||||||
|
" | " +
|
||||||
|
this.appName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,8 +672,12 @@ class ReplayWebApp extends LitElement
|
|||||||
<div class="modal-background" @click="${this.onAboutClose}"></div>
|
<div class="modal-background" @click="${this.onAboutClose}"></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<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 ${
|
||||||
<button class="delete" aria-label="close" @click="${this.onAboutClose}"></button>
|
IS_APP ? "App" : ""
|
||||||
|
}</p>
|
||||||
|
<button class="delete" aria-label="close" @click="${
|
||||||
|
this.onAboutClose
|
||||||
|
}"></button>
|
||||||
</header>
|
</header>
|
||||||
<section class="modal-card-body">
|
<section class="modal-card-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -570,16 +685,29 @@ class ReplayWebApp extends LitElement
|
|||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<div class="has-text-centered" style="width: 220px">
|
<div class="has-text-centered" style="width: 220px">
|
||||||
<wr-anim-logo class="logo" size="48px"></wr-anim-logo>
|
<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>
|
</div>
|
||||||
|
|
||||||
${IS_APP ? html`
|
${
|
||||||
<p>ReplayWeb.page App is a standalone app for Mac, Windows and Linux that loads web archive files provided by the user
|
IS_APP
|
||||||
and renders them for replay.</p>
|
? html`
|
||||||
|
<p>
|
||||||
` : html`
|
ReplayWeb.page App is a standalone app for Mac,
|
||||||
<p><a href="https://replayweb.page" target="_blank">ReplayWeb.page</a> is a browser-based viewer that loads web archive files provided by the user
|
Windows and Linux that loads web archive files
|
||||||
and renders them for replay in the browser.</p>`}
|
provided by the user and renders them for replay.
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: html` <p>
|
||||||
|
<a href="https://replayweb.page" target="_blank"
|
||||||
|
>ReplayWeb.page</a
|
||||||
|
>
|
||||||
|
is a browser-based viewer that loads web archive
|
||||||
|
files provided by the user and renders them for
|
||||||
|
replay in the browser.
|
||||||
|
</p>`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Full source code is available at:
|
<p>Full source code is available at:
|
||||||
@@ -604,7 +732,9 @@ class ReplayWebApp extends LitElement
|
|||||||
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
</details>
|
</details>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<a class="button is-warning" href="#" @click="${this.onAboutClose}">Close</a>
|
<a class="button is-warning" href="#" @click="${
|
||||||
|
this.onAboutClose
|
||||||
|
}">Close</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+79
-34
@@ -3,10 +3,8 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Chooser extends LitElement
|
class Chooser extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -30,9 +28,9 @@ class Chooser extends LitElement
|
|||||||
"application/wacz": [".wacz"],
|
"application/wacz": [".wacz"],
|
||||||
"application/wbn": [".wbn"],
|
"application/wbn": [".wbn"],
|
||||||
"application/json": [".json"],
|
"application/json": [".json"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ class Chooser extends LitElement
|
|||||||
fileDisplayName: { type: String },
|
fileDisplayName: { type: String },
|
||||||
droppedFile: { type: File },
|
droppedFile: { type: File },
|
||||||
newFullImport: { type: Boolean },
|
newFullImport: { type: Boolean },
|
||||||
noHead: { type: Boolean }
|
noHead: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +50,20 @@ class Chooser extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDropFile() {
|
onDropFile() {
|
||||||
const allowedFileExtensions = this.showOpenFilePickerOptions.types.map(type => type.accept).map(Object.values).flat(2);
|
const allowedFileExtensions = this.showOpenFilePickerOptions.types
|
||||||
|
.map((type) => type.accept)
|
||||||
|
.map(Object.values)
|
||||||
|
.flat(2);
|
||||||
|
|
||||||
const fileHasAllowedExtension = allowedFileExtensions.some(extension => this.droppedFile.name.endsWith(extension));
|
const fileHasAllowedExtension = allowedFileExtensions.some((extension) =>
|
||||||
|
this.droppedFile.name.endsWith(extension),
|
||||||
|
);
|
||||||
|
|
||||||
if (fileHasAllowedExtension) {
|
if (fileHasAllowedExtension) {
|
||||||
this.setFile(this.droppedFile);
|
this.setFile(this.droppedFile);
|
||||||
this.dispatchEvent(new CustomEvent("did-drop-file", { bubbles: true, composed: true }));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("did-drop-file", { bubbles: true, composed: true }),
|
||||||
|
);
|
||||||
this.onStartLoad(); // Automatically load the file
|
this.onStartLoad(); // Automatically load the file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +86,9 @@ class Chooser extends LitElement
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [fileHandle] = await window.showOpenFilePicker(this.showOpenFilePickerOptions);
|
const [fileHandle] = await window.showOpenFilePicker(
|
||||||
|
this.showOpenFilePickerOptions,
|
||||||
|
);
|
||||||
this.fileHandle = fileHandle;
|
this.fileHandle = fileHandle;
|
||||||
|
|
||||||
this.file = await fileHandle.getFile();
|
this.file = await fileHandle.getFile();
|
||||||
@@ -89,7 +96,10 @@ class Chooser extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
randomId() {
|
randomId() {
|
||||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
return (
|
||||||
|
Math.random().toString(36).substring(2, 15) +
|
||||||
|
Math.random().toString(36).substring(2, 15)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStartLoad(event) {
|
onStartLoad(event) {
|
||||||
@@ -121,7 +131,13 @@ class Chooser extends LitElement
|
|||||||
|
|
||||||
loadInfo.newFullImport = this.newFullImport;
|
loadInfo.newFullImport = this.newFullImport;
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("load-start", {bubbles: true, composed: true, detail: loadInfo}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("load-start", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: loadInfo,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -129,7 +145,11 @@ class Chooser extends LitElement
|
|||||||
onInput(event) {
|
onInput(event) {
|
||||||
this.fileDisplayName = event.currentTarget.value;
|
this.fileDisplayName = event.currentTarget.value;
|
||||||
|
|
||||||
if (this.file && this.fileDisplayName && this.fileDisplayName.startsWith("file://")) {
|
if (
|
||||||
|
this.file &&
|
||||||
|
this.fileDisplayName &&
|
||||||
|
this.fileDisplayName.startsWith("file://")
|
||||||
|
) {
|
||||||
this.file = null;
|
this.file = null;
|
||||||
this.fileDisplayName = "";
|
this.fileDisplayName = "";
|
||||||
}
|
}
|
||||||
@@ -144,8 +164,8 @@ class Chooser extends LitElement
|
|||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
}
|
}
|
||||||
.less-padding {
|
.less-padding {
|
||||||
padding-top: 1.0em;
|
padding-top: 1em;
|
||||||
padding-bottom: 1.0em;
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
div.field.has-addons {
|
div.field.has-addons {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
@@ -192,42 +212,67 @@ class Chooser extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html` <section
|
||||||
<section class="section ${this.noHead ? "is-paddingless" : "less-padding"}">
|
class="section ${this.noHead ? "is-paddingless" : "less-padding"}"
|
||||||
|
>
|
||||||
<div class="${this.noHead ? "" : "panel"}">
|
<div class="${this.noHead ? "" : "panel"}">
|
||||||
<div class="${this.noHead ? "is-hidden" : "panel-heading"} heading-size">${this.newFullImport ? "Import Existing" : "Load"} Web Archive</div>
|
<div
|
||||||
<div class="${this.noHead ? "" : "panel-body extra-padding"} file has-name">
|
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"
|
||||||
|
>
|
||||||
<form class="is-flex" @submit="${this.onStartLoad}">
|
<form class="is-flex" @submit="${this.onStartLoad}">
|
||||||
<label class="file-label">
|
<label class="file-label">
|
||||||
${!this.hasNativeFS ? html`
|
${!this.hasNativeFS
|
||||||
<input class="file-input"
|
? html` <input
|
||||||
@click="${(e) => e.currentTarget.value = null}"
|
class="file-input"
|
||||||
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ""}
|
@click="${(e) => (e.currentTarget.value = null)}"
|
||||||
|
@change=${this.onChooseFile}
|
||||||
|
type="file"
|
||||||
|
id="fileupload"
|
||||||
|
name="fileupload"
|
||||||
|
/>`
|
||||||
|
: ""}
|
||||||
<span class="file-cta" @click="${this.onChooseNativeFile}">
|
<span class="file-cta" @click="${this.onChooseNativeFile}">
|
||||||
<span class="file-icon">
|
<span class="file-icon">
|
||||||
<fa-icon size="0.9em" .svg=${fasUpload} aria-hidden="true"></fa-icon>
|
<fa-icon
|
||||||
</span>
|
size="0.9em"
|
||||||
<span class="file-label is-hidden-touch">
|
.svg=${fasUpload}
|
||||||
Choose File...
|
aria-hidden="true"
|
||||||
|
></fa-icon>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="file-label is-hidden-touch"> Choose File... </span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<p class="control is-expanded">
|
<p class="control is-expanded">
|
||||||
<input class="file-name input" type="text"
|
<input
|
||||||
name="filename" id="filename"
|
class="file-name input"
|
||||||
pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn|json)([?#].*)?)|(googledrive:\/\/.+)|(ssb:\/\/.+)"
|
type="text"
|
||||||
|
name="filename"
|
||||||
|
id="filename"
|
||||||
|
pattern="((file|http|https|ipfs|s3)://.*.(warc|warc.gz|zip|wacz|har|wbn|json)([?#].*)?)|(googledrive://.+)|(ssb://.+)"
|
||||||
.value="${this.fileDisplayName}"
|
.value="${this.fileDisplayName}"
|
||||||
@input="${this.onInput}"
|
@input="${this.onInput}"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
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"}">
|
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>
|
</p>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="submit" class="button is-hidden-mobile is-primary">${this.newFullImport ? "Import" : "Load"}</button>
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="button is-hidden-mobile is-primary"
|
||||||
|
>
|
||||||
|
${this.newFullImport ? "Import" : "Load"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+140
-73
@@ -10,10 +10,8 @@ import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-
|
|||||||
|
|
||||||
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class CollIndex extends LitElement
|
class CollIndex extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -36,17 +34,13 @@ class CollIndex extends LitElement
|
|||||||
|
|
||||||
get sortKeys() {
|
get sortKeys() {
|
||||||
return [
|
return [
|
||||||
{key: "title",
|
{ key: "title", name: "Title" },
|
||||||
name: "Title"},
|
|
||||||
|
|
||||||
{key: "sourceUrl",
|
{ key: "sourceUrl", name: "Source" },
|
||||||
name: "Source"},
|
|
||||||
|
|
||||||
{key: "ctime",
|
{ key: "ctime", name: this.dateName },
|
||||||
name: this.dateName},
|
|
||||||
|
|
||||||
{key: "size",
|
{ key: "size", name: "Total Size" },
|
||||||
name: "Total Size"}
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +85,12 @@ class CollIndex extends LitElement
|
|||||||
this.filteredColls = [];
|
this.filteredColls = [];
|
||||||
|
|
||||||
for (const coll of this.colls) {
|
for (const coll of this.colls) {
|
||||||
if (coll.sourceUrl.indexOf(this.query) >= 0 ||
|
if (
|
||||||
|
coll.sourceUrl.indexOf(this.query) >= 0 ||
|
||||||
coll.filename.indexOf(this.query) >= 0 ||
|
coll.filename.indexOf(this.query) >= 0 ||
|
||||||
(coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) ||
|
(coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) ||
|
||||||
(coll.title && coll.title.indexOf(this.query) >= 0)) {
|
(coll.title && coll.title.indexOf(this.query) >= 0)
|
||||||
|
) {
|
||||||
this.filteredColls.push(coll);
|
this.filteredColls.push(coll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +110,6 @@ class CollIndex extends LitElement
|
|||||||
|
|
||||||
this._deleting = {};
|
this._deleting = {};
|
||||||
this.sortedColls = [];
|
this.sortedColls = [];
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// likely no sw registered yet, or waiting for new sw to register, retry again
|
// likely no sw registered yet, or waiting for new sw to register, retry again
|
||||||
setTimeout(() => this.loadColls(), 500);
|
setTimeout(() => this.loadColls(), 500);
|
||||||
@@ -165,7 +160,7 @@ class CollIndex extends LitElement
|
|||||||
padding: 2em;
|
padding: 2em;
|
||||||
}
|
}
|
||||||
.no-top-padding {
|
.no-top-padding {
|
||||||
padding-top: 1.0em;
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
.panel-heading {
|
.panel-heading {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
@@ -253,7 +248,9 @@ class CollIndex extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
return html`<h2 class="panel-heading panel-color"><span>${this.headerName}</span></h2>`;
|
return html`<h2 class="panel-heading panel-color">
|
||||||
|
<span>${this.headerName}</span>
|
||||||
|
</h2>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSearchHeader() {
|
renderSearchHeader() {
|
||||||
@@ -269,48 +266,79 @@ class CollIndex extends LitElement
|
|||||||
</header>
|
</header>
|
||||||
<section class="section no-top-padding">
|
<section class="section no-top-padding">
|
||||||
<div class="sort-header is-small">
|
<div class="sort-header is-small">
|
||||||
${hasHeader ? html`
|
${hasHeader
|
||||||
<button @click=${() => this.hideHeader = !this.hideHeader} class="collapse button is-small">
|
? html`
|
||||||
<span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span>
|
<button @click=${() =>
|
||||||
<span>${this.hideHeader ? "Show " : "Hide"} <span class="is-sr-only">Header</span></span>
|
(this.hideHeader =
|
||||||
</button>` : ""}
|
!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>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
${this.renderHeader()}
|
${this.renderHeader()}
|
||||||
${this.colls.length ? html`
|
${this.colls.length
|
||||||
|
? html`
|
||||||
<div class="panel-block sort-header is-small">
|
<div class="panel-block sort-header is-small">
|
||||||
${this.renderSearchHeader()}
|
${this.renderSearchHeader()}
|
||||||
<div class="control has-icons-left has-addons">
|
<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"
|
<input
|
||||||
placeholder="Search by Archive Title or Source">
|
type="text"
|
||||||
<span class="icon is-left is-small"><fa-icon .svg="${fasSearch}"/></span>
|
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>
|
||||||
</div>
|
</div>
|
||||||
<wr-sorter id="index"
|
<wr-sorter
|
||||||
|
id="index"
|
||||||
sortKey="ctime"
|
sortKey="ctime"
|
||||||
?sortDesc="${true}"
|
?sortDesc="${true}"
|
||||||
.sortKeys="${this.sortKeys}"
|
.sortKeys="${this.sortKeys}"
|
||||||
.data="${this.filteredColls}"
|
.data="${this.filteredColls}"
|
||||||
@sort-changed="${(e) => this.sortedColls = e.detail.sortedData}">
|
@sort-changed="${(e) =>
|
||||||
|
(this.sortedColls = e.detail.sortedData)}"
|
||||||
|
>
|
||||||
</wr-sorter>
|
</wr-sorter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="coll-list">
|
<div class="coll-list">
|
||||||
${this.sortedColls && this.sortedColls.map((coll, i) => html`
|
${this.sortedColls &&
|
||||||
|
this.sortedColls.map(
|
||||||
|
(coll, i) => html`
|
||||||
<div class="coll-block panel-block">
|
<div class="coll-block panel-block">
|
||||||
${this.renderCollInfo(coll)}
|
${this.renderCollInfo(coll)}
|
||||||
${!this._deleting[coll.sourceUrl] ? html`
|
${!this._deleting[coll.sourceUrl]
|
||||||
<button class="delete delete-button" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
|
? html`
|
||||||
` : html`
|
<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-button is-loading is-static">Deleting</span`}
|
<span class="button delete-button is-loading is-static">Deleting</span`}
|
||||||
</div>
|
</div>
|
||||||
`)}
|
`,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
`
|
||||||
` : html`
|
: html`
|
||||||
|
|
||||||
<div class="panel-block extra-padding">
|
<div class="panel-block extra-padding">
|
||||||
${this.sortedColls === null ? html`<i>Loading Archives...</i>` :
|
${this.sortedColls === null
|
||||||
this.renderEmpty()}
|
? html`<i>Loading Archives...</i>`
|
||||||
|
: this.renderEmpty()}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -323,14 +351,15 @@ class CollIndex extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEmpty() {
|
renderEmpty() {
|
||||||
return html`<i>No Archives so far! Archives loaded in the section above will appear here.</i>`;
|
return html`<i
|
||||||
|
>No Archives so far! Archives loaded in the section above will appear
|
||||||
|
here.</i
|
||||||
|
>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class CollInfo extends LitElement
|
class CollInfo extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.detailed = false;
|
this.detailed = false;
|
||||||
@@ -341,7 +370,7 @@ class CollInfo extends LitElement
|
|||||||
return {
|
return {
|
||||||
coll: { type: Object },
|
coll: { type: Object },
|
||||||
detailed: { type: Boolean },
|
detailed: { type: Boolean },
|
||||||
canDelete: { type: Boolean }
|
canDelete: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,13 +404,14 @@ class CollInfo extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.col-title:hover {
|
.col-title:hover {
|
||||||
|
|
||||||
}
|
}
|
||||||
.col-title a {
|
.col-title a {
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.column:hover > .copy, .source-text:hover + .copy, .copy:hover {
|
.column:hover > .copy,
|
||||||
|
.source-text:hover + .copy,
|
||||||
|
.copy:hover {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
.copy {
|
.copy {
|
||||||
@@ -407,29 +437,42 @@ class CollInfo extends LitElement
|
|||||||
renderSource(coll) {
|
renderSource(coll) {
|
||||||
return html`
|
return html`
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
<span class="source-text"><p class="minihead">Source</p>
|
<span class="source-text"
|
||||||
${coll.sourceUrl && (coll.sourceUrl.startsWith("http://") || coll.sourceUrl.startsWith("https://")) ? html`
|
><p class="minihead">Source</p>
|
||||||
<a href="${coll.sourceUrl}">${coll.sourceUrl} </a>` : html`
|
${coll.sourceUrl &&
|
||||||
${coll.sourceUrl} `}
|
(coll.sourceUrl.startsWith("http://") ||
|
||||||
|
coll.sourceUrl.startsWith("https://"))
|
||||||
|
? html` <a href="${coll.sourceUrl}">${coll.sourceUrl} </a>`
|
||||||
|
: html` ${coll.sourceUrl} `}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a>
|
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"
|
||||||
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`
|
><fa-icon .svg="${fasCopy}"
|
||||||
<p><i>(${coll.filename})</i></p>` : ""}
|
/></a>
|
||||||
|
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://")
|
||||||
|
? html` <p><i>(${coll.filename})</i></p>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div class="column is-2">
|
||||||
|
<p class="minihead">Date Loaded</p>
|
||||||
|
${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}
|
||||||
|
</div>
|
||||||
|
<div class="column is-2">
|
||||||
|
<p class="minihead">Total Size</p>
|
||||||
|
${prettyBytes(Number(coll.size || 0))}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-2"><p class="minihead">Date Loaded</p>${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}</div>
|
|
||||||
<div class="column is-2"><p class="minihead">Total Size</p>${prettyBytes(Number(coll.size || 0))}</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSummaryView() {
|
renderSummaryView() {
|
||||||
const coll = this.coll;
|
const coll = this.coll;
|
||||||
|
|
||||||
return html`
|
return html` <div class="columns">
|
||||||
<div class="columns">
|
|
||||||
<div class="column col-title is-4">
|
<div class="column col-title is-4">
|
||||||
<span class="subtitle has-text-weight-bold">
|
<span class="subtitle has-text-weight-bold">
|
||||||
<a href="?source=${encodeURIComponent(coll.sourceUrl)}">${coll.title || coll.filename}</a>
|
<a href="?source=${encodeURIComponent(coll.sourceUrl)}"
|
||||||
|
>${coll.title || coll.filename}</a
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
${this.renderSource(coll)}
|
${this.renderSource(coll)}
|
||||||
@@ -439,46 +482,70 @@ class CollInfo extends LitElement
|
|||||||
renderDetailed() {
|
renderDetailed() {
|
||||||
const coll = this.coll;
|
const coll = this.coll;
|
||||||
|
|
||||||
let {numValid, numInvalid, domain, certFingerprint, datapackageHash, publicKey, software} = this.coll.verify || {};
|
let {
|
||||||
|
numValid,
|
||||||
|
numInvalid,
|
||||||
|
domain,
|
||||||
|
certFingerprint,
|
||||||
|
datapackageHash,
|
||||||
|
publicKey,
|
||||||
|
software,
|
||||||
|
} = this.coll.verify || {};
|
||||||
numValid = numValid || 0;
|
numValid = numValid || 0;
|
||||||
numInvalid = numInvalid || 0;
|
numInvalid = numInvalid || 0;
|
||||||
|
|
||||||
const certFingerprintUrl = certFingerprint ? `https://crt.sh/?q=${certFingerprint}` : "";
|
const certFingerprintUrl = certFingerprint
|
||||||
|
? `https://crt.sh/?q=${certFingerprint}`
|
||||||
|
: "";
|
||||||
|
|
||||||
return html`
|
return html` <div class="columns">
|
||||||
<div class="columns">
|
|
||||||
<div class="column col-title is-4">
|
<div class="column col-title is-4">
|
||||||
<span class="subtitle has-text-weight-bold">
|
<span class="subtitle has-text-weight-bold">
|
||||||
${coll.title || coll.filename}
|
${coll.title || coll.filename}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
${coll.desc ? html`
|
${coll.desc
|
||||||
<div class="column">
|
? html` <div class="column">
|
||||||
<p class="minihead">Description</p>
|
<p class="minihead">Description</p>
|
||||||
${coll.desc}
|
${coll.desc}
|
||||||
</div>` : html`
|
</div>`
|
||||||
`}
|
: html``}
|
||||||
<div class="column"><p class="minihead">Filename</p>${coll.filename}</div>
|
<div class="column">
|
||||||
|
<p class="minihead">Filename</p>
|
||||||
|
${coll.filename}
|
||||||
|
</div>
|
||||||
${this.renderSource(coll)}
|
${this.renderSource(coll)}
|
||||||
|
${domain
|
||||||
${domain ? html`
|
? html`
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="minihead">Observed By</p>
|
<p class="minihead">Observed By</p>
|
||||||
<p>${domain}</p>
|
<p>${domain}</p>
|
||||||
${certFingerprintUrl ? html`<span><a target="_blank" href="${certFingerprintUrl}">View Certificate</a></span>` : ""}
|
${certFingerprintUrl
|
||||||
|
? html`<span
|
||||||
|
><a target="_blank" href="${certFingerprintUrl}"
|
||||||
|
>View Certificate</a
|
||||||
|
></span
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
` : software ? html`
|
`
|
||||||
|
: software
|
||||||
|
? html`
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="minihead">Created With</p>
|
<p class="minihead">Created With</p>
|
||||||
${software || "Unknown"}
|
${software || "Unknown"}
|
||||||
</div>
|
</div>
|
||||||
` : ""}
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<p class="minihead">Validation</p>
|
<p class="minihead">Validation</p>
|
||||||
${numValid > 0 || numInvalid > 0 ? html`
|
${numValid > 0 || numInvalid > 0
|
||||||
<p>${numValid} hashes verified${numInvalid ? html`, ${numInvalid} invalid` : ""}</p>` : html`
|
? html` <p>
|
||||||
Not Available`}
|
${numValid} hashes
|
||||||
|
verified${numInvalid ? html`, ${numInvalid} invalid` : ""}
|
||||||
|
</p>`
|
||||||
|
: html` Not Available`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|||||||
+553
-164
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,10 @@
|
|||||||
import { ElectronReplayApp } from "./electron-replay-app";
|
import { ElectronReplayApp } from "./electron-replay-app";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const replayApp = new ElectronReplayApp({
|
const replayApp = new ElectronReplayApp({
|
||||||
staticPath: path.join(__dirname, "../"),
|
staticPath: path.join(__dirname, "../"),
|
||||||
profileName: "replaywebpage"
|
profileName: "replaywebpage",
|
||||||
});
|
});
|
||||||
|
|
||||||
replayApp.init(true);
|
replayApp.init(true);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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 = {};
|
const dbs = {};
|
||||||
|
|
||||||
@@ -46,7 +46,3 @@ async function getResponse(event, request, coll, ts, channel) {
|
|||||||
ipcRenderer.on("getresponse", getResponse);
|
ipcRenderer.on("getresponse", getResponse);
|
||||||
|
|
||||||
export { getDB, getColl, loader };
|
export { getDB, getColl, loader };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+79
-25
@@ -3,7 +3,15 @@
|
|||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { Headers } from "node-fetch";
|
import { Headers } from "node-fetch";
|
||||||
|
|
||||||
import {app, session, BrowserWindow, ipcMain, protocol, screen, shell } from "electron";
|
import {
|
||||||
|
app,
|
||||||
|
session,
|
||||||
|
BrowserWindow,
|
||||||
|
ipcMain,
|
||||||
|
protocol,
|
||||||
|
screen,
|
||||||
|
shell,
|
||||||
|
} from "electron";
|
||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -30,8 +38,7 @@ const FILE_PROTO = "file2";
|
|||||||
const URL_RX = /([^/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
|
const URL_RX = /([^/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class ElectronReplayApp
|
class ElectronReplayApp {
|
||||||
{
|
|
||||||
constructor({ staticPath = "./", profileName = "" } = {}) {
|
constructor({ staticPath = "./", profileName = "" } = {}) {
|
||||||
this.pluginPath = "";
|
this.pluginPath = "";
|
||||||
|
|
||||||
@@ -76,7 +83,9 @@ class ElectronReplayApp
|
|||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
if (!gotTheLock) {
|
if (!gotTheLock) {
|
||||||
console.log("App already running, opening new window in first instance and quitting");
|
console.log(
|
||||||
|
"App already running, opening new window in first instance and quitting",
|
||||||
|
);
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} else {
|
||||||
app.on("second-instance", (event, commandLine /*, workingDir*/) => {
|
app.on("second-instance", (event, commandLine /*, workingDir*/) => {
|
||||||
@@ -93,10 +102,14 @@ class ElectronReplayApp
|
|||||||
console.log("user data", app.getPath("userData"));
|
console.log("user data", app.getPath("userData"));
|
||||||
|
|
||||||
if (this.profileName) {
|
if (this.profileName) {
|
||||||
app.setPath("userData", path.join(app.getPath("appData"), this.profileName));
|
app.setPath(
|
||||||
|
"userData",
|
||||||
|
path.join(app.getPath("appData"), this.profileName),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol.registerSchemesAsPrivileged([{
|
protocol.registerSchemesAsPrivileged([
|
||||||
|
{
|
||||||
scheme: FILE_PROTO,
|
scheme: FILE_PROTO,
|
||||||
privileges: {
|
privileges: {
|
||||||
standard: false,
|
standard: false,
|
||||||
@@ -105,9 +118,10 @@ class ElectronReplayApp
|
|||||||
allowServiceWorkers: true,
|
allowServiceWorkers: true,
|
||||||
supportFetchAPI: true,
|
supportFetchAPI: true,
|
||||||
corsEnabled: true,
|
corsEnabled: true,
|
||||||
stream: true
|
stream: true,
|
||||||
}
|
},
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
app.on("will-finish-launching", () => {
|
app.on("will-finish-launching", () => {
|
||||||
app.on("open-file", (event, filePath) => {
|
app.on("open-file", (event, filePath) => {
|
||||||
@@ -168,9 +182,13 @@ class ElectronReplayApp
|
|||||||
|
|
||||||
const sesh = session.defaultSession;
|
const sesh = session.defaultSession;
|
||||||
|
|
||||||
sesh.protocol.interceptStreamProtocol("http", (request, callback) => this.doIntercept(request, callback));
|
sesh.protocol.interceptStreamProtocol("http", (request, callback) =>
|
||||||
|
this.doIntercept(request, callback),
|
||||||
|
);
|
||||||
|
|
||||||
protocol.registerStreamProtocol(FILE_PROTO, (request, callback) => this.doHandleFile(request, callback));
|
protocol.registerStreamProtocol(FILE_PROTO, (request, callback) =>
|
||||||
|
this.doHandleFile(request, callback),
|
||||||
|
);
|
||||||
|
|
||||||
this.origUA = sesh.getUserAgent();
|
this.origUA = sesh.getUserAgent();
|
||||||
|
|
||||||
@@ -200,13 +218,19 @@ class ElectronReplayApp
|
|||||||
|
|
||||||
const size = stat.size;
|
const size = stat.size;
|
||||||
|
|
||||||
const {statusCode, start, end} = this.parseRange(reqHeaders, headers, size);
|
const { statusCode, start, end } = this.parseRange(
|
||||||
|
reqHeaders,
|
||||||
|
headers,
|
||||||
|
size,
|
||||||
|
);
|
||||||
|
|
||||||
const data = request.method === "HEAD" ? null : fs.createReadStream(filename, {start, end});
|
const data =
|
||||||
|
request.method === "HEAD"
|
||||||
|
? null
|
||||||
|
: fs.createReadStream(filename, { start, end });
|
||||||
|
|
||||||
callback({ statusCode, headers, data });
|
callback({ statusCode, headers, data });
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return this.notFound("No Resource Specified", callback);
|
return this.notFound("No Resource Specified", callback);
|
||||||
}
|
}
|
||||||
@@ -235,7 +259,6 @@ class ElectronReplayApp
|
|||||||
return await this.resolveArchiveResponse(request, callback);
|
return await this.resolveArchiveResponse(request, callback);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// try serve static file from app dir
|
// try serve static file from app dir
|
||||||
let filename = request.url.slice(STATIC_PREFIX.length).split("?", 1)[0];
|
let filename = request.url.slice(STATIC_PREFIX.length).split("?", 1)[0];
|
||||||
filename = filename.split("#", 1)[0];
|
filename = filename.split("#", 1)[0];
|
||||||
@@ -261,7 +284,11 @@ class ElectronReplayApp
|
|||||||
|
|
||||||
const data = fs.createReadStream(fullPath);
|
const data = fs.createReadStream(fullPath);
|
||||||
|
|
||||||
return callback({statusCode: 200, headers: {"content-type": mimeType}, data});
|
return callback({
|
||||||
|
statusCode: 200,
|
||||||
|
headers: { "content-type": mimeType },
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +307,9 @@ class ElectronReplayApp
|
|||||||
let headers = request.headers;
|
let headers = request.headers;
|
||||||
const { method, url, uploadData } = request;
|
const { method, url, uploadData } = request;
|
||||||
|
|
||||||
const body = uploadData ? Readable.from(readBody(uploadData, session.defaultSession)) : null;
|
const body = uploadData
|
||||||
|
? Readable.from(readBody(uploadData, session.defaultSession))
|
||||||
|
: null;
|
||||||
|
|
||||||
if (this.origUA) {
|
if (this.origUA) {
|
||||||
// pass UA if origUA is set
|
// pass UA if origUA is set
|
||||||
@@ -305,8 +334,14 @@ class ElectronReplayApp
|
|||||||
|
|
||||||
notFound(url, callback) {
|
notFound(url, callback) {
|
||||||
console.log("not found: " + url);
|
console.log("not found: " + url);
|
||||||
const data = this._bufferToStream(`Sorry, the url <b>${url}</b> could not be found in this archive.`);
|
const data = this._bufferToStream(
|
||||||
callback({statusCode: 404, headers: {"Content-Type": "text/html; charset=\"utf-8\""}, data});
|
`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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveArchiveResponse(request, callback) {
|
async resolveArchiveResponse(request, callback) {
|
||||||
@@ -324,7 +359,13 @@ class ElectronReplayApp
|
|||||||
headers = new Headers(headers);
|
headers = new Headers(headers);
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
let response = new ArchiveResponse({payload, headers, status, date, url});
|
let response = new ArchiveResponse({
|
||||||
|
payload,
|
||||||
|
headers,
|
||||||
|
status,
|
||||||
|
date,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
|
||||||
const rewriter = new Rewriter({
|
const rewriter = new Rewriter({
|
||||||
baseUrl: url,
|
baseUrl: url,
|
||||||
@@ -332,7 +373,7 @@ class ElectronReplayApp
|
|||||||
urlRewrite: false,
|
urlRewrite: false,
|
||||||
contentRewrite: true,
|
contentRewrite: true,
|
||||||
decode: true,
|
decode: true,
|
||||||
useBaseRules: true
|
useBaseRules: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
request.headers = new Headers(request.headers);
|
request.headers = new Headers(request.headers);
|
||||||
@@ -345,7 +386,11 @@ class ElectronReplayApp
|
|||||||
let data = await response.getBuffer();
|
let data = await response.getBuffer();
|
||||||
|
|
||||||
if (status === 206 || status === 200) {
|
if (status === 206 || status === 200) {
|
||||||
const {statusCode, start, end} = this.parseRange(request.headers, headers, data.length);
|
const { statusCode, start, end } = this.parseRange(
|
||||||
|
request.headers,
|
||||||
|
headers,
|
||||||
|
data.length,
|
||||||
|
);
|
||||||
if (start !== undefined) {
|
if (start !== undefined) {
|
||||||
data = data.slice(start, end);
|
data = data.slice(start, end);
|
||||||
}
|
}
|
||||||
@@ -358,11 +403,16 @@ class ElectronReplayApp
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.mainWindow) {
|
if (this.mainWindow) {
|
||||||
this.mainWindow.webContents.send("getresponse", request, this.proxyColl, this.proxyTS, channel);
|
this.mainWindow.webContents.send(
|
||||||
|
"getresponse",
|
||||||
|
request,
|
||||||
|
this.proxyColl,
|
||||||
|
this.proxyTS,
|
||||||
|
channel,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +468,11 @@ class ElectronReplayApp
|
|||||||
getOpenUrl(argv) {
|
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]);
|
const filename =
|
||||||
|
this.openNextFile ||
|
||||||
|
argv.filename ||
|
||||||
|
argv.f ||
|
||||||
|
(argv._.length && argv._[0]);
|
||||||
this.openNextFile = null;
|
this.openNextFile = null;
|
||||||
|
|
||||||
let sourceString = "";
|
let sourceString = "";
|
||||||
|
|||||||
+62
-32
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import btGlobe from "../assets/globe.svg";
|
import btGlobe from "../assets/globe.svg";
|
||||||
import btAngleDoubleDown from "../assets/chevron-double-down.svg";
|
import btAngleDoubleDown from "../assets/chevron-double-down.svg";
|
||||||
import btAngleDoubleUp from "../assets/chevron-double-up.svg";
|
import btAngleDoubleUp from "../assets/chevron-double-up.svg";
|
||||||
@@ -11,10 +10,8 @@ import { LitElement, html, css } from "lit";
|
|||||||
import { tsToDate } from "./pageutils";
|
import { tsToDate } from "./pageutils";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
export class RWPEmbedReceipt extends LitElement
|
export class RWPEmbedReceipt extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.collInfo = null;
|
this.collInfo = null;
|
||||||
@@ -35,7 +32,7 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
url: { type: String },
|
url: { type: String },
|
||||||
ts: { type: String },
|
ts: { type: String },
|
||||||
|
|
||||||
active: {type: Boolean }
|
active: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
display: block;
|
display: block;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: all .3s linear;
|
transition: all 0.3s linear;
|
||||||
transform-origin: left top;
|
transform-origin: left top;
|
||||||
transform: scaleY(0);
|
transform: scaleY(0);
|
||||||
transition: all 300ms cubic-bezier(0.15, 0, 0.1, 1);
|
transition: all 300ms cubic-bezier(0.15, 0, 0.1, 1);
|
||||||
@@ -81,7 +78,7 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 1rem);
|
||||||
height: 42px;
|
height: 42px;
|
||||||
border-color: #D1D5DA;
|
border-color: #d1d5da;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
@@ -92,7 +89,7 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
transition-duration: 50ms;
|
transition-duration: 50ms;
|
||||||
transition-timing-function: ease-out;
|
transition-timing-function: ease-out;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 2
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.embed-info:active {
|
button.embed-info:active {
|
||||||
@@ -141,7 +138,7 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
color: #24292E;
|
color: #24292e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-info-drop-statscontainer > h3 {
|
.embed-info-drop-statscontainer > h3 {
|
||||||
@@ -173,37 +170,52 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
.embed-globe {
|
.embed-globe {
|
||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
background-color: #0366D6;
|
background-color: #0366d6;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
color: white;
|
color: white;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: #D1D5DA;
|
border-color: #d1d5da;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
line-height: 0.5em;
|
line-height: 0.5em;
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {numValid, numInvalid, domain, certFingerprint, datapackageHash, publicKey, software} = this.collInfo.verify || {};
|
let {
|
||||||
|
numValid,
|
||||||
|
numInvalid,
|
||||||
|
domain,
|
||||||
|
certFingerprint,
|
||||||
|
datapackageHash,
|
||||||
|
publicKey,
|
||||||
|
software,
|
||||||
|
} = this.collInfo.verify || {};
|
||||||
numValid = numValid || 0;
|
numValid = numValid || 0;
|
||||||
numInvalid = numInvalid || 0;
|
numInvalid = numInvalid || 0;
|
||||||
|
|
||||||
const sourceUrl = this.collInfo.sourceUrl;
|
const sourceUrl = this.collInfo.sourceUrl;
|
||||||
|
|
||||||
const certFingerprintUrl = certFingerprint ? `https://crt.sh/?q=${certFingerprint}` : "";
|
const certFingerprintUrl = certFingerprint
|
||||||
|
? `https://crt.sh/?q=${certFingerprint}`
|
||||||
|
: "";
|
||||||
|
|
||||||
const dateStr = tsToDate(this.ts).toLocaleString();
|
const dateStr = tsToDate(this.ts).toLocaleString();
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="dropdown mb-4 ${this.active ? "is-active" : ""}">
|
<div class="dropdown mb-4 ${this.active ? "is-active" : ""}">
|
||||||
<div class="dropdown-trigger embed-info-container">
|
<div class="dropdown-trigger embed-info-container">
|
||||||
<button class="embed-info is-small is-rounded mt-4" aria-haspopup="true" aria-controls="embed-dropdown" @click="${this.onEmbedDrop}">
|
<button class="embed-info is-small is-rounded mt-4" aria-haspopup="true" aria-controls="embed-dropdown" @click="${
|
||||||
|
this.onEmbedDrop
|
||||||
|
}">
|
||||||
<fa-icon class="menu-logo mr-2 embed-globe" size="1rem" aria-hidden="true" .svg=${btGlobe}></fa-icon>
|
<fa-icon class="menu-logo mr-2 embed-globe" size="1rem" aria-hidden="true" .svg=${btGlobe}></fa-icon>
|
||||||
<span class="embed-info-buttontext">
|
<span class="embed-info-buttontext">
|
||||||
This embed is part of a web archive. Click here to learn more.
|
This embed is part of a web archive. Click here to learn more.
|
||||||
</span>
|
</span>
|
||||||
<span class="icon is-small mr-4 ml-2">
|
<span class="icon is-small mr-4 ml-2">
|
||||||
<fa-icon title="Toggle" .svg="${this.active ? btAngleDoubleUp : btAngleDoubleDown}" aria-hidden="true"></fa-icon>
|
<fa-icon title="Toggle" .svg="${
|
||||||
|
this.active ? btAngleDoubleUp : btAngleDoubleDown
|
||||||
|
}" aria-hidden="true"></fa-icon>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,33 +240,51 @@ export class RWPEmbedReceipt extends LitElement
|
|||||||
<p><a target="_blank" href="${this.url}">${this.url}</a></p>
|
<p><a target="_blank" href="${this.url}">${this.url}</a></p>
|
||||||
<h3 class="mt-2">Archived On:</h3>
|
<h3 class="mt-2">Archived On:</h3>
|
||||||
<p>${dateStr}</p>
|
<p>${dateStr}</p>
|
||||||
${domain ? html`
|
${
|
||||||
|
domain
|
||||||
|
? html`
|
||||||
<h3 class="mt-2">Observed By:</h3>
|
<h3 class="mt-2">Observed By:</h3>
|
||||||
<p>${domain}</p>
|
<p>${domain}</p>
|
||||||
${certFingerprintUrl ? html`
|
${certFingerprintUrl
|
||||||
<p><a target="_blank" href="${certFingerprintUrl}">View Certificate</a></p>` : ""}
|
? html` <p>
|
||||||
` : software ? html`
|
<a target="_blank" href="${certFingerprintUrl}"
|
||||||
<h3 class="mt-2">Created With:</h3>
|
>View Certificate</a
|
||||||
<p>${software}</p>` : ""}
|
>
|
||||||
${!domain && publicKey ? html`
|
</p>`
|
||||||
<h3 class="mt-2">Observer Public Key:</h3>
|
: ""}
|
||||||
<p class="show-key">${publicKey}</p>` : ""}
|
`
|
||||||
|
: software
|
||||||
|
? html` <h3 class="mt-2">Created With:</h3>
|
||||||
|
<p>${software}</p>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
${
|
||||||
|
!domain && publicKey
|
||||||
|
? html` <h3 class="mt-2">Observer Public Key:</h3>
|
||||||
|
<p class="show-key">${publicKey}</p>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
<h3 class="mt-2">Validation:</h3>
|
<h3 class="mt-2">Validation:</h3>
|
||||||
${numValid > 0 || numInvalid > 0 ? html`
|
${
|
||||||
<p>${numValid} hashes verified${numInvalid ? html`, ${numInvalid} invalid` : ""}</p>` : html`
|
numValid > 0 || numInvalid > 0
|
||||||
<p>Not Available</p>
|
? html` <p>
|
||||||
`}
|
${numValid} hashes
|
||||||
|
verified${numInvalid ? html`, ${numInvalid} invalid` : ""}
|
||||||
|
</p>`
|
||||||
|
: html` <p>Not Available</p> `
|
||||||
|
}
|
||||||
<h3 class="mt-2">Package Hash:</h3>
|
<h3 class="mt-2">Package Hash:</h3>
|
||||||
<p class="show-hash">${datapackageHash}</p>
|
<p class="show-hash">${datapackageHash}</p>
|
||||||
<h3 class="mt-2">Size</h3>
|
<h3 class="mt-2">Size</h3>
|
||||||
<p>${prettyBytes(Number(this.collInfo.size || 0))}</p>
|
<p>${prettyBytes(Number(this.collInfo.size || 0))}</p>
|
||||||
</div>
|
</div>
|
||||||
${sourceUrl ? html`
|
${sourceUrl ? html`` : ""}
|
||||||
` : ""}
|
|
||||||
<p class="is-size-7 is-flex is-justify-content-space-between" style="margin-top: 40px">
|
<p class="is-size-7 is-flex is-justify-content-space-between" style="margin-top: 40px">
|
||||||
<span>
|
<span>
|
||||||
<a class="has-text-black" target="_blank" href="https://github.com/webrecorder/replayweb.page">
|
<a class="has-text-black" target="_blank" href="https://github.com/webrecorder/replayweb.page">
|
||||||
<fa-icon class="menu-logo mr-1" size="1.0rem" aria-hidden="true" .svg=${this.appLogo}></fa-icon>
|
<fa-icon class="menu-logo mr-1" size="1.0rem" aria-hidden="true" .svg=${
|
||||||
|
this.appLogo
|
||||||
|
}></fa-icon>
|
||||||
Powered by ReplayWeb.page
|
Powered by ReplayWeb.page
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+42
-22
@@ -4,17 +4,14 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
|||||||
import { wrapCss, rwpLogo } from "./misc";
|
import { wrapCss, rwpLogo } from "./misc";
|
||||||
import { SWManager } from "./swmanager";
|
import { SWManager } from "./swmanager";
|
||||||
|
|
||||||
|
|
||||||
var scriptSrc = document.currentScript && document.currentScript.src;
|
var scriptSrc = document.currentScript && document.currentScript.src;
|
||||||
|
|
||||||
var defaultReplayFile = "";
|
var defaultReplayFile = "";
|
||||||
|
|
||||||
const DEFAULT_REPLAY_BASE = "https://replayweb.page/";
|
const DEFAULT_REPLAY_BASE = "https://replayweb.page/";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Embed extends LitElement
|
class Embed extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.replaybase = "./replay/";
|
this.replaybase = "./replay/";
|
||||||
@@ -109,7 +106,12 @@ class Embed extends LitElement
|
|||||||
const scope = this.replaybase;
|
const scope = this.replaybase;
|
||||||
const requireSubdomainIframe = this.requireSubdomainIframe;
|
const requireSubdomainIframe = this.requireSubdomainIframe;
|
||||||
|
|
||||||
this.swmanager = new SWManager({name, scope, requireSubdomainIframe, appName});
|
this.swmanager = new SWManager({
|
||||||
|
name,
|
||||||
|
scope,
|
||||||
|
requireSubdomainIframe,
|
||||||
|
appName,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.swmanager.register();
|
await this.swmanager.register();
|
||||||
@@ -144,7 +146,9 @@ class Embed extends LitElement
|
|||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
if (this.noSandbox) {
|
if (this.noSandbox) {
|
||||||
console.warn("The noSandbox flag is deprecated. ReplayWeb.page does not add a sandbox by default. To enable sandboxing, use 'sandbox' flag instead. This may result in PDFs not loading and pages opening in new windows, but may be more secure in some situations");
|
console.warn(
|
||||||
|
"The noSandbox flag is deprecated. ReplayWeb.page does not add a sandbox by default. To enable sandboxing, use 'sandbox' flag instead. This may result in PDFs not loading and pages opening in new windows, but may be more secure in some situations",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.doRegister();
|
this.doRegister();
|
||||||
|
|
||||||
@@ -163,7 +167,10 @@ class Embed extends LitElement
|
|||||||
// https://lil.law.harvard.edu/blog/2022/09/15/opportunities-and-challenges-of-client-side-playback/
|
// https://lil.law.harvard.edu/blog/2022/09/15/opportunities-and-challenges-of-client-side-playback/
|
||||||
|
|
||||||
// likely safari < 16, don't use web workers due to issues with split storage state
|
// likely safari < 16, don't use web workers due to issues with split storage state
|
||||||
if (window.GestureEvent !== undefined && window.SharedWorker === undefined ){
|
if (
|
||||||
|
window.GestureEvent !== undefined &&
|
||||||
|
window.SharedWorker === undefined
|
||||||
|
) {
|
||||||
this.noWebWorker = true;
|
this.noWebWorker = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,13 +211,14 @@ class Embed extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
if (changedProperties.has("url") ||
|
if (
|
||||||
|
changedProperties.has("url") ||
|
||||||
changedProperties.has("ts") ||
|
changedProperties.has("ts") ||
|
||||||
changedProperties.has("query") ||
|
changedProperties.has("query") ||
|
||||||
changedProperties.has("view") ||
|
changedProperties.has("view") ||
|
||||||
changedProperties.has("source") ||
|
changedProperties.has("source") ||
|
||||||
changedProperties.has("src")) {
|
changedProperties.has("src")
|
||||||
|
) {
|
||||||
this.embed = this.embed || "default";
|
this.embed = this.embed || "default";
|
||||||
|
|
||||||
if (this.src) {
|
if (this.src) {
|
||||||
@@ -265,7 +273,7 @@ class Embed extends LitElement
|
|||||||
url: this.url,
|
url: this.url,
|
||||||
ts: this.ts,
|
ts: this.ts,
|
||||||
query: this.query,
|
query: this.query,
|
||||||
view: this.view
|
view: this.view,
|
||||||
}).toString();
|
}).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,17 +308,25 @@ class Embed extends LitElement
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
${this.paramString && this.hashString && this.inited ? html`
|
${this.paramString && this.hashString && this.inited
|
||||||
<iframe sandbox="${ifDefined(this.sandbox ?
|
? html`
|
||||||
"allow-downloads allow-modals allow-orientation-lock allow-pointer-lock\
|
<iframe
|
||||||
|
sandbox="${ifDefined(
|
||||||
|
this.sandbox
|
||||||
|
? "allow-downloads allow-modals allow-orientation-lock allow-pointer-lock\
|
||||||
allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts\
|
allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts\
|
||||||
allow-same-origin allow-forms" : undefined)}"
|
allow-same-origin allow-forms"
|
||||||
|
: undefined,
|
||||||
@load="${this.onLoad}" src="${this.replaybase}${this.replayfile}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen"
|
)}"
|
||||||
title="Replay of ${this.title ? `${this.title}:` :""} ${this.url}"></iframe>
|
@load="${this.onLoad}"
|
||||||
|
src="${this.replaybase}${this.replayfile}?${this
|
||||||
` : html``}
|
.paramString}#${this.hashString}"
|
||||||
|
allow="autoplay *; fullscreen"
|
||||||
|
title="Replay of ${this.title ? `${this.title}:` : ""} ${this
|
||||||
|
.url}"
|
||||||
|
></iframe>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
${this.errorMessage}
|
${this.errorMessage}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -323,7 +339,11 @@ class Embed extends LitElement
|
|||||||
const win = event.target.contentWindow;
|
const win = event.target.contentWindow;
|
||||||
const doc = event.target.contentDocument;
|
const doc = event.target.contentDocument;
|
||||||
|
|
||||||
if (win.navigator.serviceWorker && !win.navigator.serviceWorker.controller && this.reloadCount <= 2) {
|
if (
|
||||||
|
win.navigator.serviceWorker &&
|
||||||
|
!win.navigator.serviceWorker.controller &&
|
||||||
|
this.reloadCount <= 2
|
||||||
|
) {
|
||||||
this.reloadCount++;
|
this.reloadCount++;
|
||||||
setTimeout(() => win.location.reload(), 100);
|
setTimeout(() => win.location.reload(), 100);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+49
-26
@@ -3,10 +3,8 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class GDrive extends LitElement
|
class GDrive extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = "trypublic";
|
this.state = "trypublic";
|
||||||
@@ -20,7 +18,7 @@ class GDrive extends LitElement
|
|||||||
state: { type: String },
|
state: { type: String },
|
||||||
sourceUrl: { type: String },
|
sourceUrl: { type: String },
|
||||||
error: { type: Boolean },
|
error: { type: Boolean },
|
||||||
reauth: { type: Boolean }
|
reauth: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,9 +76,12 @@ class GDrive extends LitElement
|
|||||||
const extra = { publicUrl };
|
const extra = { publicUrl };
|
||||||
const size = Number(json.size);
|
const size = Number(json.size);
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("load-ready", {detail: {name, extra, size, sourceUrl}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("load-ready", {
|
||||||
|
detail: { name, extra, size, sourceUrl },
|
||||||
|
}),
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -112,11 +113,14 @@ class GDrive extends LitElement
|
|||||||
const fileId = sourceUrl.slice("googledrive://".length);
|
const fileId = sourceUrl.slice("googledrive://".length);
|
||||||
const metadataUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`;
|
const metadataUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`;
|
||||||
const authToken = response.access_token;
|
const authToken = response.access_token;
|
||||||
const headers = {"Authorization": `Bearer ${authToken}`};
|
const headers = { Authorization: `Bearer ${authToken}` };
|
||||||
|
|
||||||
const resp = await fetch(metadataUrl + "?fields=name,size&supportsAllDrives=true", {headers});
|
const resp = await fetch(
|
||||||
|
metadataUrl + "?fields=name,size&supportsAllDrives=true",
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
if ((resp.status === 404 || resp.status == 403)) {
|
if (resp.status === 404 || resp.status == 403) {
|
||||||
if (this.state !== "implicitonly") {
|
if (this.state !== "implicitonly") {
|
||||||
this.state = "trymanual";
|
this.state = "trymanual";
|
||||||
}
|
}
|
||||||
@@ -130,7 +134,11 @@ class GDrive extends LitElement
|
|||||||
const name = metadata.name;
|
const name = metadata.name;
|
||||||
const size = Number(metadata.size);
|
const size = Number(metadata.size);
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("load-ready", {detail: {sourceUrl, headers, size, name}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("load-ready", {
|
||||||
|
detail: { sourceUrl, headers, size, name },
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
@@ -138,21 +146,33 @@ class GDrive extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html` ${this.script()}
|
||||||
${this.script()}
|
${this.state !== "trymanual"
|
||||||
${this.state !== "trymanual" ? html`
|
? html` <p>Connecting to Google Drive...</p> `
|
||||||
<p>Connecting to Google Drive...</p>
|
: html`
|
||||||
` : html`
|
${this.error
|
||||||
${this.error ? html`
|
? html`
|
||||||
<div class="error has-text-danger">
|
<div class="error has-text-danger">
|
||||||
<p>${this.reauth ? "Some resources are loaded on demand from Google Drive, which requires reauthorization." :
|
<p>
|
||||||
"Could not access this file with the current Google Drive account."}</p>
|
${this.reauth
|
||||||
<p>If you have multiple Google Drive accounts, be sure to select the correct one.</p>
|
? "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>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
` : ""}
|
`
|
||||||
<button class="button is-warning is-rounded" @click="${this.onClickAuth}">
|
: ""}
|
||||||
<span class="icon"><fa-icon .svg="${fabGoogleDrive}"></fa-icon></span>
|
<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>
|
<span>Authorize Google Drive</span>
|
||||||
</button>
|
</button>
|
||||||
`}`;
|
`}`;
|
||||||
@@ -163,20 +183,23 @@ class GDrive extends LitElement
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.onload = (() => this.onLoad());
|
script.onload = () => this.onLoad();
|
||||||
script.src = "https://apis.google.com/js/platform.js";
|
script.src = "https://apis.google.com/js/platform.js";
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
gauth(prompt, callback) {
|
gauth(prompt, callback) {
|
||||||
self.gapi.load("auth2", () => {
|
self.gapi.load("auth2", () => {
|
||||||
self.gapi.auth2.authorize({
|
self.gapi.auth2.authorize(
|
||||||
|
{
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
client_id: __GDRIVE_CLIENT_ID__,
|
client_id: __GDRIVE_CLIENT_ID__,
|
||||||
scope: "https://www.googleapis.com/auth/drive.file",
|
scope: "https://www.googleapis.com/auth/drive.file",
|
||||||
response_type: "token",
|
response_type: "token",
|
||||||
prompt
|
prompt,
|
||||||
}, callback);
|
},
|
||||||
|
callback,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-43
@@ -5,10 +5,8 @@ import prettyBytes from "pretty-bytes";
|
|||||||
|
|
||||||
import { parseURLSchemeHostPath } from "./pageutils";
|
import { parseURLSchemeHostPath } from "./pageutils";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Loader extends LitElement
|
class Loader extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
@@ -84,7 +82,10 @@ class Loader extends LitElement
|
|||||||
this.fileHandle = event.data.fileHandle;
|
this.fileHandle = event.data.fileHandle;
|
||||||
if (this.error === "missing_local_file") {
|
if (this.error === "missing_local_file") {
|
||||||
this.tryFileHandle = false;
|
this.tryFileHandle = false;
|
||||||
} else if (this.error === "permission_needed" && event.data.fileHandle) {
|
} else if (
|
||||||
|
this.error === "permission_needed" &&
|
||||||
|
event.data.fileHandle
|
||||||
|
) {
|
||||||
this.state = "permission_needed";
|
this.state = "permission_needed";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -104,7 +105,9 @@ class Loader extends LitElement
|
|||||||
}
|
}
|
||||||
this.progress = this.total;
|
this.progress = this.total;
|
||||||
this.percent = 100;
|
this.percent = 100;
|
||||||
this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("coll-loaded", { detail: event.data }),
|
||||||
|
);
|
||||||
|
|
||||||
if (!this.noWebWorker) {
|
if (!this.noWebWorker) {
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
@@ -146,9 +149,11 @@ class Loader extends LitElement
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "s3":
|
case "s3":
|
||||||
source = {sourceUrl,
|
source = {
|
||||||
|
sourceUrl,
|
||||||
loadUrl: `https://${host}.s3.amazonaws.com${path}`,
|
loadUrl: `https://${host}.s3.amazonaws.com${path}`,
|
||||||
name: this.sourceUrl};
|
name: this.sourceUrl,
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "file":
|
case "file":
|
||||||
@@ -190,7 +195,11 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
extraConfig = this.loadInfo.extraConfig;
|
extraConfig = this.loadInfo.extraConfig;
|
||||||
}
|
}
|
||||||
// todo: too special case?
|
// todo: too special case?
|
||||||
if (sourceUrl.startsWith("proxy:") && extraConfig && extraConfig.recording) {
|
if (
|
||||||
|
sourceUrl.startsWith("proxy:") &&
|
||||||
|
extraConfig &&
|
||||||
|
extraConfig.recording
|
||||||
|
) {
|
||||||
type = "recordingproxy";
|
type = "recordingproxy";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,12 +210,14 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
extraConfig,
|
extraConfig,
|
||||||
type,
|
type,
|
||||||
skipExisting: true,
|
skipExisting: true,
|
||||||
file: source
|
file: source,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!navigator.serviceWorker.controller) {
|
if (!navigator.serviceWorker.controller) {
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
navigator.serviceWorker.addEventListener("controllerchange", () => resolve());
|
navigator.serviceWorker.addEventListener("controllerchange", () =>
|
||||||
|
resolve(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,14 +230,14 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
// ping service worker with messages to avoid shutdown while loading
|
// ping service worker with messages to avoid shutdown while loading
|
||||||
// (mostly for Firefox)
|
// (mostly for Firefox)
|
||||||
this.pingInterval = setInterval(() => {
|
this.pingInterval = setInterval(() => {
|
||||||
navigator.serviceWorker.controller.postMessage({"msg_type": "ping"});
|
navigator.serviceWorker.controller.postMessage({ msg_type: "ping" });
|
||||||
}, 15000);
|
}, 15000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
googledriveInit() {
|
googledriveInit() {
|
||||||
this._gdWait = new Promise((resolve) => this._gdResolve = resolve);
|
this._gdWait = new Promise((resolve) => (this._gdResolve = resolve));
|
||||||
return this._gdWait;
|
return this._gdWait;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +255,7 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = {"msg_type": "cancelLoad", "name": this.coll};
|
const msg = { msg_type: "cancelLoad", name: this.coll };
|
||||||
|
|
||||||
if (!this.noWebWorker) {
|
if (!this.noWebWorker) {
|
||||||
this.worker.postMessage(msg);
|
this.worker.postMessage(msg);
|
||||||
@@ -260,7 +271,10 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
if (this.sourceUrl && changedProperties.has("sourceUrl") || changedProperties.has("tryFileHandle")) {
|
if (
|
||||||
|
(this.sourceUrl && changedProperties.has("sourceUrl")) ||
|
||||||
|
changedProperties.has("tryFileHandle")
|
||||||
|
) {
|
||||||
this.doLoad();
|
this.doLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,10 +332,11 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
<div class="has-text-centered is-flex">
|
<div class="has-text-centered is-flex">
|
||||||
<wr-anim-logo class="logo" size="96px" />
|
<wr-anim-logo class="logo" size="96px" />
|
||||||
</div>
|
</div>
|
||||||
${!this.embed ? html`
|
${!this.embed
|
||||||
<div class="level">
|
? html` <div class="level">
|
||||||
<p class="level-item">Loading <b>${this.sourceUrl}</b>...</p>
|
<p class="level-item">Loading <b>${this.sourceUrl}</b>...</p>
|
||||||
</div>` : ""}
|
</div>`
|
||||||
|
: ""}
|
||||||
<div class="level">
|
<div class="level">
|
||||||
<div class="level-item has-text-centered">
|
<div class="level-item has-text-centered">
|
||||||
${this.renderContent()}
|
${this.renderContent()}
|
||||||
@@ -334,48 +349,70 @@ You can select a file to upload from the main page by clicking the 'Choose File.
|
|||||||
renderContent() {
|
renderContent() {
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case "googledrive":
|
case "googledrive":
|
||||||
return html`<wr-gdrive .sourceUrl=${this.sourceUrl} @load-ready=${this.onLoadReady}/>`;
|
return html`<wr-gdrive
|
||||||
|
.sourceUrl=${this.sourceUrl}
|
||||||
|
@load-ready=${this.onLoadReady}
|
||||||
|
/>`;
|
||||||
|
|
||||||
case "started":
|
case "started":
|
||||||
return html`
|
return html` <div class="progress-div">
|
||||||
<div class="progress-div">
|
<progress
|
||||||
<progress id="progress" class="progress is-primary is-large"
|
id="progress"
|
||||||
value="${this.percent}" max="100"></progress>
|
class="progress is-primary is-large"
|
||||||
|
value="${this.percent}"
|
||||||
|
max="100"
|
||||||
|
></progress>
|
||||||
<label class="progress-label" for="progress">${this.percent}%</label>
|
<label class="progress-label" for="progress">${this.percent}%</label>
|
||||||
${this.currentSize && this.totalSize ? html`
|
${this.currentSize && this.totalSize
|
||||||
<div class="loaded-prog">Loaded <b>${prettyBytes(this.currentSize)}</b> of <b>${prettyBytes(this.totalSize)}</b>
|
? html` <div class="loaded-prog">
|
||||||
${this.extraMsg && html`
|
Loaded <b>${prettyBytes(this.currentSize)}</b> of
|
||||||
<p class="extra-msg">(${this.extraMsg})</p>
|
<b>${prettyBytes(this.totalSize)}</b>
|
||||||
`}
|
${this.extraMsg &&
|
||||||
</div>` : html``}
|
html` <p class="extra-msg">(${this.extraMsg})</p> `}
|
||||||
|
</div>`
|
||||||
${!this.embed ? html`
|
: html``}
|
||||||
<button @click="${this.onCancel}" class="button is-danger">Cancel</button>` : ""}
|
${!this.embed
|
||||||
|
? html` <button @click="${this.onCancel}" class="button is-danger">
|
||||||
|
Cancel
|
||||||
|
</button>`
|
||||||
|
: ""}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
case "errored":
|
case "errored":
|
||||||
return html`
|
return html` <div class="has-text-left">
|
||||||
<div class="has-text-left">
|
|
||||||
<div class="error has-text-danger">${this.error}</div>
|
<div class="error has-text-danger">${this.error}</div>
|
||||||
<div>
|
<div>
|
||||||
${this.errorAllowRetry ? html`
|
${this.errorAllowRetry
|
||||||
<a class="button is-warning" @click=${() => window.parent.location.reload()}>Try Again</a>` : ""}
|
? html` <a
|
||||||
${this.embed ? html`` : html`
|
class="button is-warning"
|
||||||
<a href="/" class="button is-warning">Back</a>`}
|
@click=${() => window.parent.location.reload()}
|
||||||
|
>Try Again</a
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
${this.embed
|
||||||
|
? html``
|
||||||
|
: html` <a href="/" class="button is-warning">Back</a>`}
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
case "permission_needed":
|
case "permission_needed":
|
||||||
return html`
|
return html` <div class="has-text-left">
|
||||||
<div class="has-text-left">
|
<div class="">
|
||||||
<div class="">Permission is needed to reload the archive file. (Click <i>Cancel</i> to cancel loading this archive.)</div>
|
Permission is needed to reload the archive file. (Click
|
||||||
<button @click="${this.onAskPermission}" class="button is-primary">Show Permission</button>
|
<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>
|
<a href="/" class="button is-danger">Cancel</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
case "waiting":
|
case "waiting":
|
||||||
default:
|
default:
|
||||||
return html`<progress class="progress is-primary is-large" style="max-width: 400px"/>`;
|
return html`<progress
|
||||||
|
class="progress is-primary is-large"
|
||||||
|
style="max-width: 400px"
|
||||||
|
/>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+72
-33
@@ -11,14 +11,16 @@ import rwpLogo from "../assets/logo.svg";
|
|||||||
const apiPrefix = "./w/api";
|
const apiPrefix = "./w/api";
|
||||||
const replayPrefix = "./w";
|
const replayPrefix = "./w";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
const allCss = unsafeCSS(allCssRaw);
|
const allCss = unsafeCSS(allCssRaw);
|
||||||
function wrapCss(custom) {
|
function wrapCss(custom) {
|
||||||
return [allCss, 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
|
// eslint-disable-next-line no-undef
|
||||||
const VERSION = __VERSION__;
|
const VERSION = __VERSION__;
|
||||||
@@ -33,10 +35,8 @@ function clickOnSpacebarPress(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class FaIcon extends LitElement
|
class FaIcon extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.size = "1.1em";
|
this.size = "1.1em";
|
||||||
@@ -49,7 +49,7 @@ class FaIcon extends LitElement
|
|||||||
svg: { type: String },
|
svg: { type: String },
|
||||||
size: { type: String },
|
size: { type: String },
|
||||||
width: { type: String },
|
width: { type: String },
|
||||||
height: { type: String }
|
height: { type: String },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class FaIcon extends LitElement
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.0em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
:host svg {
|
:host svg {
|
||||||
fill: var(--fa-icon-fill-color, currentcolor);
|
fill: var(--fa-icon-fill-color, currentcolor);
|
||||||
@@ -88,14 +88,14 @@ class FaIcon extends LitElement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<svg style="${styleMap(styles)}"><g>${unsafeSVG(this.svg)}</g></svg>`;
|
return html`<svg style="${styleMap(styles)}">
|
||||||
|
<g>${unsafeSVG(this.svg)}</g>
|
||||||
|
</svg>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class AnimLogo extends FaIcon
|
class AnimLogo extends FaIcon {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.svg = rwpLogo;
|
this.svg = rwpLogo;
|
||||||
@@ -112,28 +112,46 @@ class AnimLogo extends FaIcon
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes animLeft {
|
@keyframes animLeft {
|
||||||
0% {stop-color: #4876ff}
|
0% {
|
||||||
25% {stop-color: #1b0921}
|
stop-color: #4876ff;
|
||||||
50% {stop-color: #4876ff}
|
}
|
||||||
75% {stop-color: #04cdff}
|
25% {
|
||||||
100% {stop-color: #4876ff}
|
stop-color: #1b0921;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stop-color: #4876ff;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stop-color: #04cdff;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stop-color: #4876ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes animRight {
|
@keyframes animRight {
|
||||||
0% {stop-color: #04cdff}
|
0% {
|
||||||
25% {stop-color: #4876ff}
|
stop-color: #04cdff;
|
||||||
50% {stop-color: #1b0921}
|
}
|
||||||
75% {stop-color: #4876ff}
|
25% {
|
||||||
100% {stop-color: #04cdff}
|
stop-color: #4876ff;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stop-color: #1b0921;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stop-color: #4876ff;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stop-color: #04cdff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class WrModal extends LitElement
|
class WrModal extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.title = "";
|
this.title = "";
|
||||||
@@ -145,14 +163,14 @@ class WrModal extends LitElement
|
|||||||
return {
|
return {
|
||||||
title: { type: String },
|
title: { type: String },
|
||||||
bgClass: { type: String },
|
bgClass: { type: String },
|
||||||
noBgClose: { type: Boolean }
|
noBgClose: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return wrapCss(css`
|
return wrapCss(css`
|
||||||
.modal-background {
|
.modal-background {
|
||||||
background-color: rgba(10, 10, 10, 0.50);
|
background-color: rgba(10, 10, 10, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-card-head {
|
.modal-card-head {
|
||||||
@@ -161,19 +179,25 @@ class WrModal extends LitElement
|
|||||||
|
|
||||||
.modal-card {
|
.modal-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--modal-width, 640px)
|
max-width: var(--modal-width, 640px);
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html` <div class="modal is-active">
|
||||||
<div class="modal is-active">
|
<div
|
||||||
<div class="modal-background" @click="${() => !this.noBgClose && this.onClose()}"></div>
|
class="modal-background"
|
||||||
|
@click="${() => !this.noBgClose && this.onClose()}"
|
||||||
|
></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head ${this.bgClass}">
|
<header class="modal-card-head ${this.bgClass}">
|
||||||
<p class="modal-card-title is-3">${this.title}</p>
|
<p class="modal-card-title is-3">${this.title}</p>
|
||||||
<button class="delete" aria-label="close" @click="${this.onClose}"></button>
|
<button
|
||||||
|
class="delete"
|
||||||
|
aria-label="close"
|
||||||
|
@click="${this.onClose}"
|
||||||
|
></button>
|
||||||
</header>
|
</header>
|
||||||
<section class="modal-card-body">
|
<section class="modal-card-body">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -192,5 +216,20 @@ customElements.define("wr-anim-logo", AnimLogo);
|
|||||||
|
|
||||||
customElements.define("wr-modal", WrModal);
|
customElements.define("wr-modal", WrModal);
|
||||||
|
|
||||||
export { wrapCss, IS_APP, VERSION, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo, WrModal,
|
export {
|
||||||
LitElement, html, css, unsafeCSS, unsafeSVG, apiPrefix, replayPrefix };
|
wrapCss,
|
||||||
|
IS_APP,
|
||||||
|
VERSION,
|
||||||
|
clickOnSpacebarPress,
|
||||||
|
rwpLogo,
|
||||||
|
FaIcon,
|
||||||
|
AnimLogo,
|
||||||
|
WrModal,
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
unsafeCSS,
|
||||||
|
unsafeSVG,
|
||||||
|
apiPrefix,
|
||||||
|
replayPrefix,
|
||||||
|
};
|
||||||
|
|||||||
+81
-32
@@ -8,10 +8,8 @@ import { getReplayLink } from "./pageutils";
|
|||||||
|
|
||||||
import { wrapCss } from "./misc";
|
import { wrapCss } from "./misc";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class PageEntry extends LitElement
|
class PageEntry extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.query = "";
|
this.query = "";
|
||||||
@@ -63,7 +61,7 @@ class PageEntry extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.check-select {
|
.check-select {
|
||||||
padding: 0 1.0em 0 0.5em;
|
padding: 0 1em 0 0.5em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto 0 auto 0;
|
margin: auto 0 auto 0;
|
||||||
}
|
}
|
||||||
@@ -80,7 +78,6 @@ class PageEntry extends LitElement
|
|||||||
margin-bottom: calc(-0.75rem + 2px);
|
margin-bottom: calc(-0.75rem + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.favicon {
|
.favicon {
|
||||||
width: 24px !important;
|
width: 24px !important;
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
@@ -180,22 +177,29 @@ class PageEntry extends LitElement
|
|||||||
const p = this.page;
|
const p = this.page;
|
||||||
const date = this.page.date;
|
const date = this.page.date;
|
||||||
|
|
||||||
const hasSize = typeof(p.size) === "number";
|
const hasSize = typeof p.size === "number";
|
||||||
|
|
||||||
const editable = this.editable && !this.isSidebar;
|
const editable = this.editable && !this.isSidebar;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${editable ? html`
|
${editable
|
||||||
<div class="check-select">
|
? html` <div class="check-select">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input @change=${this.onSendSelToggle} type="checkbox" .checked="${this.selected}">
|
<input
|
||||||
|
@change=${this.onSendSelToggle}
|
||||||
|
type="checkbox"
|
||||||
|
.checked="${this.selected}"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>` : ""}
|
</div>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
${this.index ? html`
|
${this.index
|
||||||
|
? html`
|
||||||
<div class="column col-index is-1 is-size-7">${this.index}.</div>
|
<div class="column col-index is-1 is-size-7">${this.index}.</div>
|
||||||
` : ""}
|
`
|
||||||
|
: ""}
|
||||||
<div class="column col-date is-2">
|
<div class="column col-date is-2">
|
||||||
<div>${date ? date.toLocaleDateString() : ""}</div>
|
<div>${date ? date.toLocaleDateString() : ""}</div>
|
||||||
<div>${date ? date.toLocaleTimeString() : ""}</div>
|
<div>${date ? date.toLocaleTimeString() : ""}</div>
|
||||||
@@ -204,38 +208,71 @@ class PageEntry extends LitElement
|
|||||||
<div class="media">
|
<div class="media">
|
||||||
<figure class="media-left">
|
<figure class="media-left">
|
||||||
<p class="">
|
<p class="">
|
||||||
${this.iconValid ? html`
|
${this.iconValid
|
||||||
<img class="favicon" @error="${() => this.iconValid = false}" src="${this.replayPrefix}/${this.page.timestamp}id_/${p.favIconUrl}"/>` : html`
|
? html` <img
|
||||||
<span class="favicon"></span>`}
|
class="favicon"
|
||||||
|
@error="${() => (this.iconValid = false)}"
|
||||||
|
src="${this.replayPrefix}/${this.page
|
||||||
|
.timestamp}id_/${p.favIconUrl}"
|
||||||
|
/>`
|
||||||
|
: html` <span class="favicon"></span>`}
|
||||||
</p>
|
</p>
|
||||||
</figure>
|
</figure>
|
||||||
<div class="media-content ${this.isCurrent ? "current" : ""}">
|
<div class="media-content ${this.isCurrent ? "current" : ""}">
|
||||||
<div role="heading" aria-level="${this.isSidebar ? "4" : "3"}">
|
<div role="heading" aria-level="${this.isSidebar ? "4" : "3"}">
|
||||||
<a @dblclick="${this.onReload}" @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">
|
<p class="is-size-6 has-text-weight-bold has-text-link text">
|
||||||
<keyword-mark keywords="${this.query}">${p.title || p.url}</keyword-mark>
|
<keyword-mark keywords="${this.query}"
|
||||||
|
>${p.title || p.url}</keyword-mark
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="has-text-dark text">
|
||||||
|
<keyword-mark keywords="${this.query}"
|
||||||
|
>${p.url}</keyword-mark
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
<p class="has-text-dark text"><keyword-mark keywords="${this.query}">${p.url}</keyword-mark></p>
|
|
||||||
<p class="has-text-grey-dark text is-inline-date">
|
<p class="has-text-grey-dark text is-inline-date">
|
||||||
${date ? date.toLocaleString() : ""}
|
${date ? date.toLocaleString() : ""}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
${this.textSnippet ? html`
|
${this.textSnippet
|
||||||
<div class="text"><keyword-mark keywords="${this.query}">${this.textSnippet}</keyword-mark></div>` : html``}
|
? html` <div class="text">
|
||||||
|
<keyword-mark keywords="${this.query}"
|
||||||
|
>${this.textSnippet}</keyword-mark
|
||||||
|
>
|
||||||
|
</div>`
|
||||||
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
${hasSize ? html`
|
${hasSize
|
||||||
<div class="media-right" style="margin-right: 2em">
|
? html` <div class="media-right" style="margin-right: 2em">
|
||||||
${prettyBytes(p.size)}
|
${prettyBytes(p.size)}
|
||||||
</div>` : ""}
|
</div>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${editable ? html`
|
${editable
|
||||||
${!this.deleting ? html`
|
? html` ${!this.deleting
|
||||||
<button @click="${this.onSendDeletePage}" class="delete delete-button"></button>` : html`
|
? html` <button
|
||||||
<button class="button is-loading delete-button is-static"></button>
|
@click="${this.onSendDeletePage}"
|
||||||
`}` : ""}
|
class="delete delete-button"
|
||||||
|
></button>`
|
||||||
|
: html`
|
||||||
|
<button
|
||||||
|
class="button is-loading delete-button is-static"
|
||||||
|
></button>
|
||||||
|
`}`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +282,9 @@ class PageEntry extends LitElement
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await fetch(`${this.replayPrefix}/${this.page.timestamp}id_/${this.page.favIconUrl}`);
|
const resp = await fetch(
|
||||||
|
`${this.replayPrefix}/${this.page.timestamp}id_/${this.page.favIconUrl}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (resp.status != 200) {
|
if (resp.status != 200) {
|
||||||
this.favIconData = null;
|
this.favIconData = null;
|
||||||
@@ -256,7 +295,9 @@ class PageEntry extends LitElement
|
|||||||
const mime = resp.headers.get("content-type");
|
const mime = resp.headers.get("content-type");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.favIconData = `data:${mime};base64,${btoa(String.fromCharCode.apply(null, payload))}`;
|
this.favIconData = `data:${mime};base64,${btoa(
|
||||||
|
String.fromCharCode.apply(null, payload),
|
||||||
|
)}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
this.favIconData = null;
|
this.favIconData = null;
|
||||||
@@ -324,7 +365,13 @@ class PageEntry extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendChangeEvent(data, reload) {
|
sendChangeEvent(data, reload) {
|
||||||
this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data, reload}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("coll-tab-nav", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: { data, reload },
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSendDeletePage() {
|
onSendDeletePage() {
|
||||||
@@ -335,7 +382,9 @@ class PageEntry extends LitElement
|
|||||||
onSendSelToggle(event) {
|
onSendSelToggle(event) {
|
||||||
const page = this.page.id;
|
const page = this.page.id;
|
||||||
const selected = event.currentTarget.checked;
|
const selected = event.currentTarget.checked;
|
||||||
this.dispatchEvent(new CustomEvent("sel-page", {detail: {page, selected}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("sel-page", { detail: { page, selected } }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+271
-108
@@ -14,10 +14,8 @@ import fasAngleDown from "@fortawesome/fontawesome-free/svgs/solid/angle-down.sv
|
|||||||
|
|
||||||
import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
|
import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Pages extends LitElement
|
class Pages extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.filteredPages = [];
|
this.filteredPages = [];
|
||||||
@@ -58,17 +56,17 @@ class Pages extends LitElement
|
|||||||
static get sortKeys() {
|
static get sortKeys() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "",
|
key: "",
|
||||||
"name": "Best Match",
|
name: "Best Match",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "title",
|
key: "title",
|
||||||
"name": "Title"
|
name: "Title",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "date",
|
key: "date",
|
||||||
"name": "Date"
|
name: "Date",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ class Pages extends LitElement
|
|||||||
editing: { type: Boolean },
|
editing: { type: Boolean },
|
||||||
|
|
||||||
toDeletePages: { type: Object },
|
toDeletePages: { type: Object },
|
||||||
toDeletePage: { type: Object }
|
toDeletePage: { type: Object },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,10 +117,8 @@ class Pages extends LitElement
|
|||||||
async updated(changedProperties) {
|
async updated(changedProperties) {
|
||||||
if (changedProperties.has("collInfo")) {
|
if (changedProperties.has("collInfo")) {
|
||||||
this.updateTextSearch();
|
this.updateTextSearch();
|
||||||
|
|
||||||
} else if (changedProperties.has("query")) {
|
} else if (changedProperties.has("query")) {
|
||||||
this.filter();
|
this.filter();
|
||||||
|
|
||||||
} else if (changedProperties.has("currList")) {
|
} else if (changedProperties.has("currList")) {
|
||||||
this.filter();
|
this.filter();
|
||||||
} else if (changedProperties.has("showAllPages")) {
|
} else if (changedProperties.has("showAllPages")) {
|
||||||
@@ -154,7 +150,11 @@ class Pages extends LitElement
|
|||||||
//if (await this.updateComplete) {
|
//if (await this.updateComplete) {
|
||||||
const selected = this.renderRoot.querySelector(".current");
|
const selected = this.renderRoot.querySelector(".current");
|
||||||
if (selected) {
|
if (selected) {
|
||||||
const opts = {behavior: "smooth", block: "nearest", inline: "nearest"};
|
const opts = {
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
};
|
||||||
setTimeout(() => selected.scrollIntoView(opts), 100);
|
setTimeout(() => selected.scrollIntoView(opts), 100);
|
||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
@@ -180,7 +180,7 @@ class Pages extends LitElement
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
if (this.flex && this.query && this.textPages) {
|
if (this.flex && this.query && this.textPages) {
|
||||||
const results = await this.flex.searchAsync(this.query, 25);
|
const results = await this.flex.searchAsync(this.query, 25);
|
||||||
this.filteredPages = results.map(inx => this.textPages[inx]);
|
this.filteredPages = results.map((inx) => this.textPages[inx]);
|
||||||
} else if (this.showAllPages && this.hasExtraPages) {
|
} else if (this.showAllPages && this.hasExtraPages) {
|
||||||
this.filteredPages = [...this.textPages];
|
this.filteredPages = [...this.textPages];
|
||||||
} else {
|
} else {
|
||||||
@@ -205,7 +205,9 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
async filterCurated() {
|
async filterCurated() {
|
||||||
const resp = await fetch(`${this.collInfo.apiPrefix}/curated/${this.currList}`);
|
const resp = await fetch(
|
||||||
|
`${this.collInfo.apiPrefix}/curated/${this.currList}`,
|
||||||
|
);
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
|
|
||||||
const curated = json.curated;
|
const curated = json.curated;
|
||||||
@@ -223,10 +225,14 @@ class Pages extends LitElement
|
|||||||
this.flex = flex;
|
this.flex = flex;
|
||||||
this.textPages = pages;
|
this.textPages = pages;
|
||||||
|
|
||||||
this.hasExtraPages = this.textPages && this.collInfo && this.collInfo.pages &&
|
this.hasExtraPages =
|
||||||
|
this.textPages &&
|
||||||
|
this.collInfo &&
|
||||||
|
this.collInfo.pages &&
|
||||||
this.textPages.length > this.collInfo.pages.length;
|
this.textPages.length > this.collInfo.pages.length;
|
||||||
|
|
||||||
return Promise.all(pages.map((page, index) => {
|
return Promise.all(
|
||||||
|
pages.map((page, index) => {
|
||||||
let text = page.url;
|
let text = page.url;
|
||||||
if (page.title) {
|
if (page.title) {
|
||||||
text += " " + page.title;
|
text += " " + page.title;
|
||||||
@@ -235,7 +241,8 @@ class Pages extends LitElement
|
|||||||
text += " " + page.text;
|
text += " " + page.text;
|
||||||
}
|
}
|
||||||
return flex.addAsync(index, text);
|
return flex.addAsync(index, text);
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTextSearch() {
|
async updateTextSearch() {
|
||||||
@@ -272,10 +279,8 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.addPages(lines);
|
await this.addPages(lines);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
await this.addPages(this.collInfo.pages);
|
await this.addPages(this.collInfo.pages);
|
||||||
@@ -298,7 +303,8 @@ class Pages extends LitElement
|
|||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[role="main"], #contents div[role="complementary"] {
|
div[role="main"],
|
||||||
|
#contents div[role="complementary"] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +348,7 @@ class Pages extends LitElement
|
|||||||
.index-bar-title {
|
.index-bar-title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-bottom: 1.0rem;
|
margin-bottom: 1rem;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +371,7 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.index-bar-menu {
|
.index-bar-menu {
|
||||||
margin-top: 1.0rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#filter-label {
|
#filter-label {
|
||||||
@@ -408,7 +414,8 @@ class Pages extends LitElement
|
|||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.sidebar) .columns.is-hidden-mobile, :host(.sidebar) .is-hidden-mobile {
|
:host(.sidebar) .columns.is-hidden-mobile,
|
||||||
|
:host(.sidebar) .is-hidden-mobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +434,7 @@ class Pages extends LitElement
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
|
||||||
padding-bottom: 1.0em;
|
padding-bottom: 1em;
|
||||||
min-height: 0px;
|
min-height: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +448,7 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
.is-disabled {
|
.is-disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: .65;
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@@ -451,12 +458,12 @@ class Pages extends LitElement
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: fit-content;
|
min-height: fit-content;
|
||||||
|
|
||||||
margin-bottom: 1.0em;
|
margin-bottom: 1em;
|
||||||
border-bottom: 3px solid rgb(237, 237, 237);
|
border-bottom: 3px solid rgb(237, 237, 237);
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-select {
|
.check-select {
|
||||||
padding: 0 1.0em 0 0.5em;
|
padding: 0 1em 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
@@ -497,7 +504,7 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
${prefix} .mobile-header {
|
${prefix} .mobile-header {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
margin-left: 1.0rem;
|
margin-left: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -507,8 +514,9 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
${prefix} .menu {
|
${prefix} .menu {
|
||||||
font-size: 0.80rem;
|
font-size: 0.8rem;
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectList(event) {
|
onSelectList(event) {
|
||||||
@@ -525,64 +533,126 @@ class Pages extends LitElement
|
|||||||
const currList = this.currList;
|
const currList = this.currList;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "2": "1"}">
|
<div
|
||||||
|
class="is-sr-only"
|
||||||
|
role="heading"
|
||||||
|
aria-level="${this.isSidebar ? "2" : "1"}"
|
||||||
|
>
|
||||||
Pages in ${this.collInfo.title}
|
Pages in ${this.collInfo.title}
|
||||||
</div>
|
</div>
|
||||||
<div class="search-bar notification is-marginless">
|
<div class="search-bar notification is-marginless">
|
||||||
${this.isSidebar ? html `<h3 class="is-sr-only">Search and Filter Pages</h3>` : ""}
|
${this.isSidebar
|
||||||
|
? html`<h3 class="is-sr-only">Search and Filter Pages</h3>`
|
||||||
|
: ""}
|
||||||
<div class="field flex-auto">
|
<div class="field flex-auto">
|
||||||
<div class="control has-icons-left ${this.loading ? "is-loading" : ""}">
|
<div
|
||||||
<input type="search" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text"
|
class="control has-icons-left ${this.loading ? "is-loading" : ""}"
|
||||||
placeholder="Search by Page URL, Title or Text">
|
>
|
||||||
<span class="icon is-left"><fa-icon .svg="${fasSearch}" aria-hidden="true"></fa-icon></span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main columns">
|
<div class="main columns">
|
||||||
<div class="column index-bar is-one-fifth ${this.isSidebar ? "is-hidden-mobile" : ""}">
|
<div
|
||||||
|
class="column index-bar is-one-fifth ${this.isSidebar
|
||||||
${this.editable && this.editing ? html`
|
? "is-hidden-mobile"
|
||||||
<form @submit="${this.onUpdateTitle}"><input id="titleEdit" class="input" value="${this.collInfo.title}" @blur="${this.onUpdateTitle}"></form>
|
: ""}"
|
||||||
` : html`
|
>
|
||||||
<div class="index-bar-title" @dblclick="${() => this.editing = true}">${this.collInfo.title}</div>`}
|
${this.editable && this.editing
|
||||||
|
? html`
|
||||||
${this.editable ? html`<fa-icon class="editIcon" .svg="${fasEdit}"></fa-icon>` : html``}
|
<form @submit="${this.onUpdateTitle}">
|
||||||
|
<input
|
||||||
${this.hasExtraPages ? html`
|
id="titleEdit"
|
||||||
<span class="check-select">
|
class="input"
|
||||||
|
value="${this.collInfo.title}"
|
||||||
|
@blur="${this.onUpdateTitle}"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
`
|
||||||
|
: html` <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``}
|
||||||
|
${this.hasExtraPages
|
||||||
|
? html` <span class="check-select">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input @change=${e => this.showAllPages = e.currentTarget.checked} type="checkbox" .checked="${this.showAllPages}">
|
<input
|
||||||
|
@change=${(e) =>
|
||||||
|
(this.showAllPages = e.currentTarget.checked)}
|
||||||
|
type="checkbox"
|
||||||
|
.checked="${this.showAllPages}"
|
||||||
|
/>
|
||||||
Show Non-Seed Pages
|
Show Non-Seed Pages
|
||||||
</label>
|
</label>
|
||||||
</span>` : ""}
|
</span>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<span class="num-results is-hidden-mobile" aria-live="polite" aria-atomic="true">${this.formatResults()}</span>
|
<span
|
||||||
|
class="num-results is-hidden-mobile"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
|
>${this.formatResults()}</span
|
||||||
|
>
|
||||||
|
|
||||||
${this.editable ? html`
|
${this.editable
|
||||||
<div class="index-bar-actions">
|
? html` <div class="index-bar-actions">
|
||||||
${this.renderDownloadMenu()}
|
${this.renderDownloadMenu()}
|
||||||
</div>` : ""}
|
</div>`
|
||||||
|
: ""}
|
||||||
${this.collInfo.lists.length ? html`
|
${this.collInfo.lists.length
|
||||||
|
? html`
|
||||||
<p id="filter-label" class="menu-label">Filter By List:</p>
|
<p id="filter-label" class="menu-label">Filter By List:</p>
|
||||||
<div class="index-bar-menu menu">
|
<div class="index-bar-menu menu">
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
<a href="#list-0" data-list="0" class="${currList === 0 ? "is-active" : ""}"
|
<a
|
||||||
@click=${this.onSelectList}><i>All Pages</i></a>
|
href="#list-0"
|
||||||
|
data-list="0"
|
||||||
|
class="${currList === 0 ? "is-active" : ""}"
|
||||||
|
@click=${this.onSelectList}
|
||||||
|
><i>All Pages</i></a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
${this.collInfo.lists.map(list => html`
|
${this.collInfo.lists.map(
|
||||||
<li>
|
(list) =>
|
||||||
<a @click=${this.onSelectList} href="#list-${list.id}"
|
html` <li>
|
||||||
|
<a
|
||||||
|
@click=${this.onSelectList}
|
||||||
|
href="#list-${list.id}"
|
||||||
data-list="${list.id}"
|
data-list="${list.id}"
|
||||||
class="${currList === list.id ? "is-active" : ""}">${list.title}</a>
|
class="${currList === list.id ? "is-active" : ""}"
|
||||||
</li>`)}
|
>${list.title}</a
|
||||||
|
>
|
||||||
|
</li>`,
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
` : ""}
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="column main-content">
|
<div class="column main-content">
|
||||||
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "3": "2"}">Page List</div>
|
<div
|
||||||
|
class="is-sr-only"
|
||||||
|
role="heading"
|
||||||
|
aria-level="${this.isSidebar ? "3" : "2"}"
|
||||||
|
>
|
||||||
|
Page List
|
||||||
|
</div>
|
||||||
${this.renderPages()}
|
${this.renderPages()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -594,7 +664,11 @@ class Pages extends LitElement
|
|||||||
return html`
|
return html`
|
||||||
<div class="dropdown ${this.menuActive ? "is-active" : ""}">
|
<div class="dropdown ${this.menuActive ? "is-active" : ""}">
|
||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
<button @click="${this.onMenu}" class="button is-small" aria-haspopup="true" aria-expanded="${this.menuActive}" aria-controls="dropdown-menu">
|
<button @click="${
|
||||||
|
this.onMenu
|
||||||
|
}" class="button is-small" aria-haspopup="true" aria-expanded="${
|
||||||
|
this.menuActive
|
||||||
|
}" aria-controls="dropdown-menu">
|
||||||
<span>Download</span>
|
<span>Download</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<fa-icon .svg="${fasAngleDown} aria-hidden="true"></fa-icon>
|
<fa-icon .svg="${fasAngleDown} aria-hidden="true"></fa-icon>
|
||||||
@@ -607,19 +681,25 @@ class Pages extends LitElement
|
|||||||
@click="${(e) => this.onDownload(e, "wacz")}"
|
@click="${(e) => this.onDownload(e, "wacz")}"
|
||||||
@keyup="${clickOnSpacebarPress}"
|
@keyup="${clickOnSpacebarPress}"
|
||||||
class="dropdown-item">
|
class="dropdown-item">
|
||||||
Download ${this.selectedPages.size === 0 ? "All" : "Selected"} as WACZ (Web Archive Collection Zip)
|
Download ${
|
||||||
|
this.selectedPages.size === 0 ? "All" : "Selected"
|
||||||
|
} as WACZ (Web Archive Collection Zip)
|
||||||
</a>
|
</a>
|
||||||
<a role="button" href="#"
|
<a role="button" href="#"
|
||||||
@click="${(e) => this.onDownload(e, "warc")}"
|
@click="${(e) => this.onDownload(e, "warc")}"
|
||||||
@keyup="${clickOnSpacebarPress}"
|
@keyup="${clickOnSpacebarPress}"
|
||||||
class="dropdown-item">
|
class="dropdown-item">
|
||||||
Download ${this.selectedPages.size === 0 ? "All" : "Selected"} as WARC 1.1 Only
|
Download ${
|
||||||
|
this.selectedPages.size === 0 ? "All" : "Selected"
|
||||||
|
} as WARC 1.1 Only
|
||||||
</a>
|
</a>
|
||||||
<a role="button" href="#"
|
<a role="button" href="#"
|
||||||
@click="${(e) => this.onDownload(e, "warc1.0")}"
|
@click="${(e) => this.onDownload(e, "warc1.0")}"
|
||||||
@keyup="${clickOnSpacebarPress}"
|
@keyup="${clickOnSpacebarPress}"
|
||||||
class="dropdown-item">
|
class="dropdown-item">
|
||||||
Download ${this.selectedPages.size === 0 ? "All" : "Selected"} as WARC 1.0 Only
|
Download ${
|
||||||
|
this.selectedPages.size === 0 ? "All" : "Selected"
|
||||||
|
} as WARC 1.0 Only
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -628,31 +708,77 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
renderPageHeader() {
|
renderPageHeader() {
|
||||||
return html`
|
return html`
|
||||||
${!this.isSidebar && this.editable && this.filteredPages.length ? html`
|
${!this.isSidebar && this.editable && this.filteredPages.length
|
||||||
<div class="check-select">
|
? html` <div class="check-select">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input @change=${this.onSelectAll} type="checkbox" .checked="${this.allSelected}">
|
<input
|
||||||
|
@change=${this.onSelectAll}
|
||||||
|
type="checkbox"
|
||||||
|
.checked="${this.allSelected}"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>` : html``}
|
</div>`
|
||||||
|
: html``}
|
||||||
|
|
||||||
<div class="header columns is-hidden-mobile">
|
<div class="header columns is-hidden-mobile">
|
||||||
${this.query ? html`
|
${this.query
|
||||||
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ""}">Match</a>` : ""}
|
? 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="date" class="column is-2 ${this.sortKey === "date" ? (this.sortDesc ? "desc" : "asc") : ""}">Date</a>
|
<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>
|
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>
|
||||||
|
|
||||||
<div class="is-hidden-tablet mobile-header">
|
<div class="is-hidden-tablet mobile-header">
|
||||||
<div class="num-results" aria-live="polite" aria-atomic="true">${this.formatResults()}</div>
|
<div class="num-results" aria-live="polite" aria-atomic="true">
|
||||||
<wr-sorter id="pages"
|
${this.formatResults()}
|
||||||
|
</div>
|
||||||
|
<wr-sorter
|
||||||
|
id="pages"
|
||||||
.sortKey="${this.sortKey}"
|
.sortKey="${this.sortKey}"
|
||||||
.sortDesc="${this.sortDesc}"
|
.sortDesc="${this.sortDesc}"
|
||||||
.sortKeys="${Pages.sortKeys}"
|
.sortKeys="${Pages.sortKeys}"
|
||||||
.data="${this.filteredPages}"
|
.data="${this.filteredPages}"
|
||||||
pageResults="100"
|
pageResults="100"
|
||||||
@sort-changed="${this.onSortChanged}"
|
@sort-changed="${this.onSortChanged}"
|
||||||
class="${this.filteredPages.length ? "" : "is-hidden"}">
|
class="${this.filteredPages.length ? "" : "is-hidden"}"
|
||||||
|
>
|
||||||
</wr-sorter>
|
</wr-sorter>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -663,17 +789,34 @@ class Pages extends LitElement
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html` <wr-modal
|
||||||
<wr-modal bgClass="has-background-grey-lighter" @modal-closed="${() => this.toDeletePages = this.toDeletePage = null}" title="Confirm Delete">
|
bgClass="has-background-grey-lighter"
|
||||||
${this.toDeletePage ? html`
|
@modal-closed="${() => (this.toDeletePages = this.toDeletePage = null)}"
|
||||||
<p>Are you sure you want to delete the page <b>${this.toDeletePage.title}</b>?
|
title="Confirm Delete"
|
||||||
(Size: <b>${prettyBytes(this.toDeletePage.size)}</b>)</p>` : html`
|
>
|
||||||
<p>Are you sure you want to delete the <b>${this.toDeletePages.size}</b> selected pages?
|
${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`
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete the
|
||||||
|
<b>${this.toDeletePages.size}</b> selected pages?
|
||||||
|
</p>
|
||||||
`}
|
`}
|
||||||
<p>This operation can not be undone.</p>
|
<p>This operation can not be undone.</p>
|
||||||
|
|
||||||
<button @click="${this.onDeletePages}"class="button is-danger">Delete</button>
|
<button @click="${this.onDeletePages}" class="button is-danger">
|
||||||
<button @click="${() => this.toDeletePages = this.toDeletePage = null}" class="button">Cancel</button>
|
Delete
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="${() => (this.toDeletePages = this.toDeletePage = null)}"
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</wr-modal>`;
|
</wr-modal>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +826,7 @@ class Pages extends LitElement
|
|||||||
let ts = page.timestamp;
|
let ts = page.timestamp;
|
||||||
if (!ts && page.date) {
|
if (!ts && page.date) {
|
||||||
ts = getTS(page.date);
|
ts = getTS(page.date);
|
||||||
} else if (typeof(page.ts) === "string") {
|
} else if (typeof page.ts === "string") {
|
||||||
ts = getTS(page.ts);
|
ts = getTS(page.ts);
|
||||||
}
|
}
|
||||||
return ts === this.ts;
|
return ts === this.ts;
|
||||||
@@ -700,12 +843,11 @@ class Pages extends LitElement
|
|||||||
${this.renderPageHeader()}
|
${this.renderPageHeader()}
|
||||||
</div>
|
</div>
|
||||||
<ul class="scroller" @scroll="${this.onScroll}">
|
<ul class="scroller" @scroll="${this.onScroll}">
|
||||||
${this.sortedPages.length ? html`
|
${this.sortedPages.length
|
||||||
${this.sortedPages.map((p, i) => {
|
? html` ${this.sortedPages.map((p, i) => {
|
||||||
const selected = this.selectedPages.has(p.id);
|
const selected = this.selectedPages.has(p.id);
|
||||||
|
|
||||||
return html`
|
return html` <li class="page-entry ${selected ? "selected" : ""}">
|
||||||
<li class="page-entry ${selected ? "selected" : ""}">
|
|
||||||
<wr-page-entry
|
<wr-page-entry
|
||||||
.index="${this.query || this.isSidebar ? i + 1 : 0}"
|
.index="${this.query || this.isSidebar ? i + 1 : 0}"
|
||||||
.editable="${this.editable}"
|
.editable="${this.editable}"
|
||||||
@@ -721,7 +863,9 @@ class Pages extends LitElement
|
|||||||
class="${this.isSidebar ? "sidebar" : ""}"
|
class="${this.isSidebar ? "sidebar" : ""}"
|
||||||
>
|
>
|
||||||
</wr-page-entry>
|
</wr-page-entry>
|
||||||
</li>`; })}` : html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
|
</li>`;
|
||||||
|
})}`
|
||||||
|
: html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
|
||||||
</ul>
|
</ul>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -743,11 +887,15 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
const body = JSON.stringify({ title });
|
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) {
|
if (res.status === 200) {
|
||||||
this.dispatchEvent(new CustomEvent("coll-update", {detail: {title}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("coll-update", { detail: { title } }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenu(event) {
|
onMenu(event) {
|
||||||
@@ -755,9 +903,13 @@ class Pages extends LitElement
|
|||||||
this.menuActive = !this.menuActive;
|
this.menuActive = !this.menuActive;
|
||||||
|
|
||||||
if (this.menuActive) {
|
if (this.menuActive) {
|
||||||
document.addEventListener("click", () => {
|
document.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
this.menuActive = false;
|
this.menuActive = false;
|
||||||
}, {once: true});
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,7 +938,7 @@ class Pages extends LitElement
|
|||||||
} else {
|
} else {
|
||||||
this.selectedPages.delete(page);
|
this.selectedPages.delete(page);
|
||||||
}
|
}
|
||||||
this.allSelected = (this.selectedPages.size === this.sortedPages.length);
|
this.allSelected = this.selectedPages.size === this.sortedPages.length;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -796,7 +948,7 @@ class Pages extends LitElement
|
|||||||
this.selectedPages.clear();
|
this.selectedPages.clear();
|
||||||
} else {
|
} else {
|
||||||
//this.selectedPages = new Set();
|
//this.selectedPages = new Set();
|
||||||
this.sortedPages.forEach(p => {
|
this.sortedPages.forEach((p) => {
|
||||||
this.selectedPages.add(p.id);
|
this.selectedPages.add(p.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -810,7 +962,10 @@ class Pages extends LitElement
|
|||||||
const selected = this.selectedPages.size > 0;
|
const selected = this.selectedPages.size > 0;
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set("pages", selected ? Array.from(this.selectedPages.keys()).join(",") : "all");
|
params.set(
|
||||||
|
"pages",
|
||||||
|
selected ? Array.from(this.selectedPages.keys()).join(",") : "all",
|
||||||
|
);
|
||||||
params.set("format", format);
|
params.set("format", format);
|
||||||
if (this.collInfo.filename) {
|
if (this.collInfo.filename) {
|
||||||
params.set("filename", this.collInfo.filename);
|
params.set("filename", this.collInfo.filename);
|
||||||
@@ -844,7 +999,9 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const id of this.toDeletePages) {
|
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();
|
const json = await resp.json();
|
||||||
|
|
||||||
if (json.error) {
|
if (json.error) {
|
||||||
@@ -894,7 +1051,10 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
getNoResultsMessage() {
|
getNoResultsMessage() {
|
||||||
if (!this.collInfo || !this.collInfo.pages.length) {
|
if (!this.collInfo || !this.collInfo.pages.length) {
|
||||||
return html`<span class="fix-text-wrapping">No Pages are defined in this archive. The archive may be empty. <a href="#view=resources">Try browsing by URL</a>.</span>`;
|
return html`<span class="fix-text-wrapping"
|
||||||
|
>No Pages are defined in this archive. The archive may be empty.
|
||||||
|
<a href="#view=resources">Try browsing by URL</a>.</span
|
||||||
|
>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.updatingSearch) {
|
if (this.updatingSearch) {
|
||||||
@@ -906,7 +1066,10 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.query) {
|
if (this.query) {
|
||||||
return html `<span class="fix-text-wrapping">No matching pages found. Try changing the search query, or <a href="#view=resources">browse by URL</a>.</span>`;
|
return html`<span class="fix-text-wrapping"
|
||||||
|
>No matching pages found. Try changing the search query, or
|
||||||
|
<a href="#view=resources">browse by URL</a>.</span
|
||||||
|
>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "No Pages Found";
|
return "No Pages Found";
|
||||||
@@ -914,7 +1077,8 @@ class Pages extends LitElement
|
|||||||
|
|
||||||
onScroll(event) {
|
onScroll(event) {
|
||||||
const element = event.currentTarget;
|
const element = event.currentTarget;
|
||||||
const diff = (element.scrollHeight - element.scrollTop) - element.clientHeight;
|
const diff =
|
||||||
|
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||||
if (diff < 40) {
|
if (diff < 40) {
|
||||||
const sorter = this.renderRoot.querySelector("wr-sorter");
|
const sorter = this.renderRoot.querySelector("wr-sorter");
|
||||||
if (sorter) {
|
if (sorter) {
|
||||||
@@ -924,7 +1088,6 @@ class Pages extends LitElement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
customElements.define("wr-page-view", Pages);
|
customElements.define("wr-page-view", Pages);
|
||||||
|
|
||||||
export { Pages };
|
export { Pages };
|
||||||
|
|||||||
+26
-15
@@ -3,11 +3,12 @@ async function digestMessage(message, hashtype) {
|
|||||||
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
|
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
|
||||||
const hashBuffer = await crypto.subtle.digest(hashtype, msgUint8); // hash the message
|
const hashBuffer = await crypto.subtle.digest(hashtype, msgUint8); // hash the message
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
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;
|
return hashHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
function tsToDate(ts) {
|
function tsToDate(ts) {
|
||||||
if (!ts) {
|
if (!ts) {
|
||||||
@@ -18,12 +19,19 @@ function tsToDate(ts) {
|
|||||||
ts += "00000101000000".substr(ts.length);
|
ts += "00000101000000".substr(ts.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
const datestr = (ts.substring(0, 4) + "-" +
|
const datestr =
|
||||||
ts.substring(4, 6) + "-" +
|
ts.substring(0, 4) +
|
||||||
ts.substring(6, 8) + "T" +
|
"-" +
|
||||||
ts.substring(8, 10) + ":" +
|
ts.substring(4, 6) +
|
||||||
ts.substring(10, 12) + ":" +
|
"-" +
|
||||||
ts.substring(12, 14) + "-00:00");
|
ts.substring(6, 8) +
|
||||||
|
"T" +
|
||||||
|
ts.substring(8, 10) +
|
||||||
|
":" +
|
||||||
|
ts.substring(10, 12) +
|
||||||
|
":" +
|
||||||
|
ts.substring(12, 14) +
|
||||||
|
"-00:00";
|
||||||
|
|
||||||
return new Date(datestr);
|
return new Date(datestr);
|
||||||
}
|
}
|
||||||
@@ -37,17 +45,15 @@ function getPageDateTS(page) {
|
|||||||
// leave date unchanged in case of error
|
// leave date unchanged in case of error
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : "";
|
const timestamp = date && !isNaN(date) ? getTS(date.toISOString()) : "";
|
||||||
return { date, timestamp };
|
return { date, timestamp };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
function getTS(iso) {
|
function getTS(iso) {
|
||||||
return iso.replace(/[-:T]/g, "").slice(0, 14);
|
return iso.replace(/[-:T]/g, "").slice(0, 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
function getReplayLink(view, url, ts) {
|
function getReplayLink(view, url, ts) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@@ -57,7 +63,6 @@ function getReplayLink(view, url, ts) {
|
|||||||
return "#" + params.toString();
|
return "#" + params.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
async function sourceToId(url) {
|
async function sourceToId(url) {
|
||||||
try {
|
try {
|
||||||
@@ -72,7 +77,6 @@ async function sourceToId(url) {
|
|||||||
return { url, coll };
|
return { url, coll };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// simple parse of scheme, host, rest of path
|
// simple parse of scheme, host, rest of path
|
||||||
// not using URL due to different browser behavior
|
// not using URL due to different browser behavior
|
||||||
@@ -105,5 +109,12 @@ function parseURLSchemeHostPath(url) {
|
|||||||
return { scheme, host, path };
|
return { scheme, host, path };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
export { digestMessage, tsToDate, getTS, getPageDateTS, getReplayLink, sourceToId, parseURLSchemeHostPath };
|
digestMessage,
|
||||||
|
tsToDate,
|
||||||
|
getTS,
|
||||||
|
getPageDateTS,
|
||||||
|
getReplayLink,
|
||||||
|
sourceToId,
|
||||||
|
parseURLSchemeHostPath,
|
||||||
|
};
|
||||||
|
|||||||
+133
-53
@@ -1,11 +1,8 @@
|
|||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { wrapCss, rwpLogo } from "./misc";
|
import { wrapCss, rwpLogo } from "./misc";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Replay extends LitElement
|
class Replay extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.replayUrl = "";
|
this.replayUrl = "";
|
||||||
@@ -39,21 +36,30 @@ class Replay extends LitElement
|
|||||||
iframeUrl: { type: String },
|
iframeUrl: { type: String },
|
||||||
|
|
||||||
showAuth: { type: Boolean },
|
showAuth: { type: Boolean },
|
||||||
authFileHandle: { type: Object }
|
authFileHandle: { type: Object },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
window.addEventListener("message", (event) => this.onReplayMessage(event));
|
window.addEventListener("message", (event) => this.onReplayMessage(event));
|
||||||
navigator.serviceWorker.addEventListener("message", (event) => this.handleAuthMessage(event));
|
navigator.serviceWorker.addEventListener("message", (event) =>
|
||||||
|
this.handleAuthMessage(event),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAuthMessage(event) {
|
async handleAuthMessage(event) {
|
||||||
if (event.data.type === "authneeded" && this.collInfo && event.data.coll === this.collInfo.coll) {
|
if (
|
||||||
|
event.data.type === "authneeded" &&
|
||||||
|
this.collInfo &&
|
||||||
|
event.data.coll === this.collInfo.coll
|
||||||
|
) {
|
||||||
if (event.data.fileHandle) {
|
if (event.data.fileHandle) {
|
||||||
this.authFileHandle = event.data.fileHandle;
|
this.authFileHandle = event.data.fileHandle;
|
||||||
try {
|
try {
|
||||||
if (await this.authFileHandle.requestPermission({mode: "read"}) === "granted") {
|
if (
|
||||||
|
(await this.authFileHandle.requestPermission({ mode: "read" })) ===
|
||||||
|
"granted"
|
||||||
|
) {
|
||||||
this.showAuth = false;
|
this.showAuth = false;
|
||||||
this.reauthWait = null;
|
this.reauthWait = null;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
@@ -75,18 +81,24 @@ class Replay extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
doSetIframeUrl() {
|
doSetIframeUrl() {
|
||||||
this.iframeUrl = this.url ? `${this.collInfo.replayPrefix}/${this.ts || ""}mp_/${this.url}` : "";
|
this.iframeUrl = this.url
|
||||||
|
? `${this.collInfo.replayPrefix}/${this.ts || ""}mp_/${this.url}`
|
||||||
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
if (changedProperties.has("sourceUrl") || changedProperties.has("collInfo")) {
|
if (
|
||||||
|
changedProperties.has("sourceUrl") ||
|
||||||
|
changedProperties.has("collInfo")
|
||||||
|
) {
|
||||||
this.reauthWait = null;
|
this.reauthWait = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.url &&
|
if (
|
||||||
((this.replayUrl != this.url) || (this.replayTS != this.ts)) &&
|
this.url &&
|
||||||
(changedProperties.has("url") || changedProperties.has("ts"))) {
|
(this.replayUrl != this.url || this.replayTS != this.ts) &&
|
||||||
|
(changedProperties.has("url") || changedProperties.has("ts"))
|
||||||
|
) {
|
||||||
this.replayUrl = this.url;
|
this.replayUrl = this.url;
|
||||||
this.replayTS = this.ts;
|
this.replayTS = this.ts;
|
||||||
this.showAuth = false;
|
this.showAuth = false;
|
||||||
@@ -98,29 +110,48 @@ class Replay extends LitElement
|
|||||||
this.waitForLoad();
|
this.waitForLoad();
|
||||||
|
|
||||||
const detail = { title: "Archived Page", replayTitle: false };
|
const detail = { title: "Archived Page", replayTitle: false };
|
||||||
this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("update-title", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.replayUrl && changedProperties.has("replayUrl")) ||
|
if (
|
||||||
(this.replayTS && changedProperties.has("replayTS"))) {
|
(this.replayUrl && changedProperties.has("replayUrl")) ||
|
||||||
|
(this.replayTS && changedProperties.has("replayTS"))
|
||||||
|
) {
|
||||||
const data = {
|
const data = {
|
||||||
url: this.replayUrl,
|
url: this.replayUrl,
|
||||||
ts: this.replayTS,
|
ts: this.replayTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {replaceLoc: true, data}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("coll-tab-nav", { detail: { replaceLoc: true, data } }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.title && (changedProperties.has("title") || changedProperties.has("actualTS"))) {
|
if (
|
||||||
|
this.title &&
|
||||||
|
(changedProperties.has("title") || changedProperties.has("actualTS"))
|
||||||
|
) {
|
||||||
const detail = {
|
const detail = {
|
||||||
title: this.title,
|
title: this.title,
|
||||||
url: this.replayUrl,
|
url: this.replayUrl,
|
||||||
// send actual ts even if live
|
// send actual ts even if live
|
||||||
ts: this.actualTS,
|
ts: this.actualTS,
|
||||||
|
|
||||||
replayTitle: true
|
replayTitle: true,
|
||||||
};
|
};
|
||||||
this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("update-title", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +167,10 @@ class Replay extends LitElement
|
|||||||
const iframe = this.renderRoot.querySelector("iframe");
|
const iframe = this.renderRoot.querySelector("iframe");
|
||||||
|
|
||||||
if (iframe && event.source === iframe.contentWindow) {
|
if (iframe && event.source === iframe.contentWindow) {
|
||||||
if (event.data.wb_type === "load" || event.data.wb_type === "replace-url") {
|
if (
|
||||||
|
event.data.wb_type === "load" ||
|
||||||
|
event.data.wb_type === "replace-url"
|
||||||
|
) {
|
||||||
this.replayTS = event.data.is_live ? "" : event.data.ts;
|
this.replayTS = event.data.is_live ? "" : event.data.ts;
|
||||||
this.actualTS = event.data.ts;
|
this.actualTS = event.data.ts;
|
||||||
this.replayUrl = event.data.url;
|
this.replayUrl = event.data.url;
|
||||||
@@ -145,7 +179,13 @@ class Replay extends LitElement
|
|||||||
|
|
||||||
if (event.data.icons) {
|
if (event.data.icons) {
|
||||||
const icons = event.data.icons;
|
const icons = event.data.icons;
|
||||||
this.dispatchEvent(new CustomEvent("replay-favicons", {bubbles: true, composed: true, detail: {icons}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("replay-favicons", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: { icons },
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (event.data.wb_type === "title") {
|
} else if (event.data.wb_type === "title") {
|
||||||
this.title = event.data.title;
|
this.title = event.data.title;
|
||||||
@@ -155,17 +195,19 @@ class Replay extends LitElement
|
|||||||
|
|
||||||
onReAuthed(event) {
|
onReAuthed(event) {
|
||||||
this.reauthWait = (async () => {
|
this.reauthWait = (async () => {
|
||||||
|
|
||||||
if (!this.authFileHandle) {
|
if (!this.authFileHandle) {
|
||||||
// google drive reauth
|
// google drive reauth
|
||||||
const headers = event.detail.headers;
|
const headers = event.detail.headers;
|
||||||
|
|
||||||
await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
|
await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({headers})
|
body: JSON.stringify({ headers }),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (await this.authFileHandle.requestPermission({mode: "read"}) !== "granted") {
|
if (
|
||||||
|
(await this.authFileHandle.requestPermission({ mode: "read" })) !==
|
||||||
|
"granted"
|
||||||
|
) {
|
||||||
this.reauthWait = null;
|
this.reauthWait = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -184,15 +226,22 @@ class Replay extends LitElement
|
|||||||
this.setLoading();
|
this.setLoading();
|
||||||
this._loadPoll = window.setInterval(() => {
|
this._loadPoll = window.setInterval(() => {
|
||||||
const iframe = this.renderRoot.querySelector("iframe");
|
const iframe = this.renderRoot.querySelector("iframe");
|
||||||
if (!iframe || !iframe.contentDocument || !iframe.contentWindow ||
|
if (
|
||||||
(iframe.contentDocument.readyState === "complete" && !iframe.contentWindow._WBWombat)) {
|
!iframe ||
|
||||||
|
!iframe.contentDocument ||
|
||||||
|
!iframe.contentWindow ||
|
||||||
|
(iframe.contentDocument.readyState === "complete" &&
|
||||||
|
!iframe.contentWindow._WBWombat)
|
||||||
|
) {
|
||||||
this.clearLoading(iframe && iframe.contentWindow);
|
this.clearLoading(iframe && iframe.contentWindow);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearLoading(iframeWin) {
|
clearLoading(iframeWin) {
|
||||||
this.dispatchEvent(new CustomEvent("replay-loading", {detail: {loading: false}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("replay-loading", { detail: { loading: false } }),
|
||||||
|
);
|
||||||
|
|
||||||
if (this._loadPoll) {
|
if (this._loadPoll) {
|
||||||
window.clearInterval(this._loadPoll);
|
window.clearInterval(this._loadPoll);
|
||||||
@@ -207,7 +256,9 @@ class Replay extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading() {
|
setLoading() {
|
||||||
this.dispatchEvent(new CustomEvent("replay-loading", {detail: {loading: true}}));
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("replay-loading", { detail: { loading: true } }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
@@ -252,13 +303,13 @@ class Replay extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.intro-panel .panel-heading {
|
.intro-panel .panel-heading {
|
||||||
font-size: 1.0em;
|
font-size: 1em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iframe-main.modal-bg {
|
.iframe-main.modal-bg {
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
background-color: rgba(10, 10, 10, 0.70)
|
background-color: rgba(10, 10, 10, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrlogo {
|
#wrlogo {
|
||||||
@@ -266,7 +317,7 @@ class Replay extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
.intro-panel .panel-block {
|
.intro-panel .panel-block {
|
||||||
padding: 1.0em;
|
padding: 1em;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
line-height: 2.5em;
|
line-height: 2.5em;
|
||||||
}
|
}
|
||||||
@@ -284,45 +335,74 @@ class Replay extends LitElement
|
|||||||
render() {
|
render() {
|
||||||
const title = `Replay of ${this.title ? `${this.title}:` : ""} ${this.url}`;
|
const title = `Replay of ${this.title ? `${this.title}:` : ""} ${this.url}`;
|
||||||
|
|
||||||
return html`
|
return html` <h1 id="replay-heading" class="is-sr-only">${title}</h1>
|
||||||
|
|
||||||
<h1 id="replay-heading" class="is-sr-only">${title}</h1>
|
${!this.iframeUrl
|
||||||
|
? html` <div class="panel intro-panel">
|
||||||
${!this.iframeUrl ? html`
|
|
||||||
<div class="panel intro-panel">
|
|
||||||
<p class="panel-heading">Replay Web Page</p>
|
<p class="panel-heading">Replay Web Page</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<p>Enter a URL above to replay it from the web archive!</p>
|
<p>Enter a URL above to replay it from the web archive!</p>
|
||||||
<p>(Or, check out <a href="#view=pages">Pages</a> or <a href="#view=resources">URLs</a> to explore the contents of this archive.)</p>
|
<p>
|
||||||
|
(Or, check out <a href="#view=pages">Pages</a> or
|
||||||
|
<a href="#view=resources">URLs</a> to explore the contents of
|
||||||
|
this archive.)
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>` : html`
|
</div>`
|
||||||
|
: html`
|
||||||
<div class="iframe-container">
|
<div class="iframe-container">
|
||||||
<iframe class="iframe-main" name="___wb_replay_top_frame" @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen
|
<iframe
|
||||||
src="${this.iframeUrl}" title="${title}"></iframe>
|
class="iframe-main"
|
||||||
|
name="___wb_replay_top_frame"
|
||||||
|
@message="${this.onReplayMessage}"
|
||||||
|
allow="autoplay 'self'; fullscreen"
|
||||||
|
allowfullscreen
|
||||||
|
src="${this.iframeUrl}"
|
||||||
|
title="${title}"
|
||||||
|
></iframe>
|
||||||
|
|
||||||
${this.showAuth ? html`
|
${this.showAuth
|
||||||
|
? html`
|
||||||
<div class="iframe-main modal-bg">
|
<div class="iframe-main modal-bg">
|
||||||
<div class="panel intro-panel">
|
<div class="panel intro-panel">
|
||||||
<p class="panel-heading">
|
<p class="panel-heading">
|
||||||
<fa-icon id="wrlogo" size="1.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
|
<fa-icon
|
||||||
|
id="wrlogo"
|
||||||
|
size="1.5rem"
|
||||||
|
.svg=${rwpLogo}
|
||||||
|
aria-hidden="true"
|
||||||
|
></fa-icon>
|
||||||
Authorization Needed
|
Authorization Needed
|
||||||
</p>
|
</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
${this.authFileHandle ? html`
|
${this.authFileHandle
|
||||||
<p>This archive is loaded from a local file: <b>${this.authFileHandle.name}</b></p>
|
? html`
|
||||||
<p>The browser needs to confirm your permission to continue loading from this file.</p>
|
<p>
|
||||||
<button class="button is-warning is-rounded" @click="${this.onReAuthed}">Show Confirmation</button>
|
This archive is loaded from a local file:
|
||||||
` : html`
|
<b>${this.authFileHandle.name}</b>
|
||||||
<wr-gdrive
|
</p>
|
||||||
|
<p>
|
||||||
|
The browser needs to confirm your permission
|
||||||
|
to continue loading from this file.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="button is-warning is-rounded"
|
||||||
|
@click="${this.onReAuthed}"
|
||||||
|
>
|
||||||
|
Show Confirmation
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: html` <wr-gdrive
|
||||||
.sourceUrl="${this.sourceUrl}"
|
.sourceUrl="${this.sourceUrl}"
|
||||||
.state="trymanual"
|
.state="trymanual"
|
||||||
.reauth="${true}"
|
.reauth="${true}"
|
||||||
@load-ready="${this.onReAuthed}"/>`}
|
@load-ready="${this.onReAuthed}"
|
||||||
|
/>`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`: ""}
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`}`;
|
`}`;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-12
@@ -4,10 +4,8 @@ import { wrapCss } from "./misc";
|
|||||||
import fasSortDown from "@fortawesome/fontawesome-free/svgs/solid/sort-down.svg";
|
import fasSortDown from "@fortawesome/fontawesome-free/svgs/solid/sort-down.svg";
|
||||||
import fasSortUp from "@fortawesome/fontawesome-free/svgs/solid/sort-up.svg";
|
import fasSortUp from "@fortawesome/fontawesome-free/svgs/solid/sort-up.svg";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Sorter extends LitElement
|
class Sorter extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.sortedData = [];
|
this.sortedData = [];
|
||||||
@@ -79,7 +77,9 @@ class Sorter extends LitElement
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.sortDesc == (first[this.sortKey] < second[this.sortKey])) ? 1 : -1;
|
return this.sortDesc == first[this.sortKey] < second[this.sortKey]
|
||||||
|
? 1
|
||||||
|
: -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,9 @@ class Sorter extends LitElement
|
|||||||
const detail = {
|
const detail = {
|
||||||
sortKey: this.sortKey,
|
sortKey: this.sortKey,
|
||||||
sortDesc: this.sortDesc,
|
sortDesc: this.sortDesc,
|
||||||
sortedData: this.numResults ? this.sortedData.slice(0, this.numResults) : this.sortedData
|
sortedData: this.numResults
|
||||||
|
? this.sortedData.slice(0, this.numResults)
|
||||||
|
: this.sortedData,
|
||||||
};
|
};
|
||||||
this.dispatchEvent(new CustomEvent("sort-changed", { detail }));
|
this.dispatchEvent(new CustomEvent("sort-changed", { detail }));
|
||||||
}
|
}
|
||||||
@@ -119,17 +121,26 @@ class Sorter extends LitElement
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="select is-small">
|
<div class="select is-small">
|
||||||
<select id="sort-select" @change=${(e) => this.sortKey = e.currentTarget.value}>
|
<select id="sort-select" @change=${(e) =>
|
||||||
${this.sortKeys.map((sort) => html`
|
(this.sortKey = e.currentTarget.value)}>
|
||||||
<option value="${sort.key}" ?selected="${sort.key === this.sortKey}">Sort By: ${sort.name}
|
${this.sortKeys.map(
|
||||||
|
(sort) => html`
|
||||||
|
<option value="${sort.key}" ?selected="${sort.key === this.sortKey}">
|
||||||
|
Sort By: ${sort.name}
|
||||||
</option>
|
</option>
|
||||||
`)}
|
`,
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button @click=${() => this.sortDesc = !this.sortDesc} class="button is-small">
|
<button @click=${() =>
|
||||||
|
(this.sortDesc = !this.sortDesc)} class="button is-small">
|
||||||
<span>Order:</span>
|
<span>Order:</span>
|
||||||
<span class="is-sr-only">${this.sortDesc ? "Ascending" : "Descending"}</span>
|
<span class="is-sr-only">${
|
||||||
<span class="icon"><fa-icon aria-hidden="true" .svg=${this.sortDesc ? fasSortUp : fasSortDown}></span>
|
this.sortDesc ? "Ascending" : "Descending"
|
||||||
|
}</span>
|
||||||
|
<span class="icon"><fa-icon aria-hidden="true" .svg=${
|
||||||
|
this.sortDesc ? fasSortUp : fasSortDown
|
||||||
|
}></span>
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-33
@@ -8,11 +8,8 @@ import { getTS, getReplayLink } from "./pageutils";
|
|||||||
|
|
||||||
import Split from "split.js";
|
import Split from "split.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class Story extends LitElement
|
class Story extends LitElement {
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -42,12 +39,13 @@ class Story extends LitElement
|
|||||||
currList: { type: Number },
|
currList: { type: Number },
|
||||||
|
|
||||||
isSidebar: { type: Boolean },
|
isSidebar: { type: Boolean },
|
||||||
splitDirection: { type: Boolean }
|
splitDirection: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
recalcSplitter(width) {
|
recalcSplitter(width) {
|
||||||
this.splitDirection = this.isSidebar || width < 769 ? "vertical" : "horizontal";
|
this.splitDirection =
|
||||||
|
this.isSidebar || width < 769 ? "vertical" : "horizontal";
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
@@ -65,7 +63,10 @@ class Story extends LitElement
|
|||||||
this.doLoadCurated();
|
this.doLoadCurated();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has("collInfo") || changedProperties.has("isSidebar")) {
|
if (
|
||||||
|
changedProperties.has("collInfo") ||
|
||||||
|
changedProperties.has("isSidebar")
|
||||||
|
) {
|
||||||
this.recalcSplitter(document.documentElement.clientWidth);
|
this.recalcSplitter(document.documentElement.clientWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ class Story extends LitElement
|
|||||||
|
|
||||||
gutterSize: 4,
|
gutterSize: 4,
|
||||||
|
|
||||||
direction: this.splitDirection
|
direction: this.splitDirection,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.splitter = Split([sidebar, content], opts);
|
this.splitter = Split([sidebar, content], opts);
|
||||||
@@ -165,14 +166,14 @@ class Story extends LitElement
|
|||||||
${Story.sidebarStyles(unsafeCSS(":host(.sidebar)"))}
|
${Story.sidebarStyles(unsafeCSS(":host(.sidebar)"))}
|
||||||
|
|
||||||
.desc p {
|
.desc p {
|
||||||
margin-bottom: 1.0em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns {
|
.columns {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
margin: 1.0em 0 0 0 !important;
|
margin: 1em 0 0 0 !important;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +219,6 @@ class Story extends LitElement
|
|||||||
.gutter.gutter-horizontal:hover {
|
.gutter.gutter-horizontal:hover {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,15 +243,20 @@ class Story extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
${prefix} .menu {
|
${prefix} .menu {
|
||||||
font-size: 0.80rem;
|
font-size: 0.8rem;
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const currListNum = this.currList;
|
const currListNum = this.currList;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="is-sr-only" role="heading" aria-level="${this.isSidebar ? "2": "1"}">
|
<div
|
||||||
|
class="is-sr-only"
|
||||||
|
role="heading"
|
||||||
|
aria-level="${this.isSidebar ? "2" : "1"}"
|
||||||
|
>
|
||||||
Story for ${this.collInfo.title}
|
Story for ${this.collInfo.title}
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
@@ -259,14 +264,28 @@ class Story extends LitElement
|
|||||||
<aside class="menu">
|
<aside class="menu">
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
<a href="#list-0" data-list="0" class="${currListNum === 0 ? "is-active" : ""} menu-label is-size-4"
|
<a
|
||||||
@click=${this.onClickScroll}>${this.collInfo.title}</a>
|
href="#list-0"
|
||||||
<ul class="menu-list">${this.collInfo.lists.map(list => html`
|
data-list="0"
|
||||||
<li>
|
class="${currListNum === 0
|
||||||
<a @click=${this.onClickScroll} href="#list-${list.id}"
|
? "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}"
|
data-list="${list.id}"
|
||||||
class="${currListNum === list.id ? "is-active" : ""}">${list.title}</a>
|
class="${currListNum === list.id ? "is-active" : ""}"
|
||||||
</li>`)}
|
>${list.title}</a
|
||||||
|
>
|
||||||
|
</li>`,
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -284,19 +303,22 @@ class Story extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderLists() {
|
renderLists() {
|
||||||
return html`
|
return html` ${this.collInfo.lists.map(
|
||||||
${this.collInfo.lists.map((list) => html`
|
(list) => html`
|
||||||
<article id="list-${list.id}">
|
<article id="list-${list.id}">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<hr />
|
<hr />
|
||||||
<h3>${list.title}</h3>
|
<h3>${list.title}</h3>
|
||||||
<p>${list.desc}</p>
|
<p>${list.desc}</p>
|
||||||
<ol>
|
<ol>
|
||||||
${this.curatedPageMap[list.id] ? this.curatedPageMap[list.id].map((p) => this.renderCPage(p)) : html``}
|
${this.curatedPageMap[list.id]
|
||||||
|
? this.curatedPageMap[list.id].map((p) => this.renderCPage(p))
|
||||||
|
: html``}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`)}`;
|
`,
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCPage(p) {
|
renderCPage(p) {
|
||||||
@@ -304,11 +326,14 @@ class Story extends LitElement
|
|||||||
|
|
||||||
const ts = getTS(date.toISOString());
|
const ts = getTS(date.toISOString());
|
||||||
|
|
||||||
return html`
|
return html` <li>
|
||||||
<li>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<a @click="${this.onReplay}" data-url="${p.url}" data-ts="${ts}"
|
<a
|
||||||
href="${getReplayLink("story", p.url, ts)}">
|
@click="${this.onReplay}"
|
||||||
|
data-url="${p.url}"
|
||||||
|
data-ts="${ts}"
|
||||||
|
href="${getReplayLink("story", p.url, ts)}"
|
||||||
|
>
|
||||||
<p class="is-size-6 has-text-weight-bold has-text-link">${p.title}</p>
|
<p class="is-size-6 has-text-weight-bold has-text-link">${p.title}</p>
|
||||||
<p class="has-text-dark">${p.url}</p>
|
<p class="has-text-dark">${p.url}</p>
|
||||||
</a>
|
</a>
|
||||||
@@ -368,11 +393,17 @@ class Story extends LitElement
|
|||||||
const currST = scrollable.scrollTop;
|
const currST = scrollable.scrollTop;
|
||||||
|
|
||||||
if (currST > this.lastST) {
|
if (currST > this.lastST) {
|
||||||
while (next.nextElementSibling && next.nextElementSibling.getBoundingClientRect().top < target) {
|
while (
|
||||||
|
next.nextElementSibling &&
|
||||||
|
next.nextElementSibling.getBoundingClientRect().top < target
|
||||||
|
) {
|
||||||
next = next.nextElementSibling;
|
next = next.nextElementSibling;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (next.previousElementSibling && next.previousElementSibling.getBoundingClientRect().bottom >= target) {
|
while (
|
||||||
|
next.previousElementSibling &&
|
||||||
|
next.previousElementSibling.getBoundingClientRect().bottom >= target
|
||||||
|
) {
|
||||||
next = next.previousElementSibling;
|
next = next.previousElementSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,11 +414,13 @@ class Story extends LitElement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((new Date().getTime() - this.clickTime) < 1000) {
|
if (new Date().getTime() - this.clickTime < 1000) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sel = this.renderRoot.querySelector(`a[data-list="${this.currList}"]`);
|
const sel = this.renderRoot.querySelector(
|
||||||
|
`a[data-list="${this.currList}"]`,
|
||||||
|
);
|
||||||
if (sel) {
|
if (sel) {
|
||||||
const opts = { behavior: "smooth", block: "nearest", inline: "nearest" };
|
const opts = { behavior: "smooth", block: "nearest", inline: "nearest" };
|
||||||
sel.scrollIntoView(opts);
|
sel.scrollIntoView(opts);
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import { SWReplay } from "@webrecorder/wabac/src/swmain";
|
|||||||
|
|
||||||
import { WorkerLoader } from "@webrecorder/wabac/src/loaders";
|
import { WorkerLoader } from "@webrecorder/wabac/src/loaders";
|
||||||
|
|
||||||
|
|
||||||
if (self.registration) {
|
if (self.registration) {
|
||||||
const staticData = new Map();
|
const staticData = new Map();
|
||||||
|
|
||||||
const prefix = self.registration.scope;
|
const prefix = self.registration.scope;
|
||||||
|
|
||||||
staticData.set(prefix, { type: "text/html", content: INDEX_HTML });
|
staticData.set(prefix, { type: "text/html", content: INDEX_HTML });
|
||||||
staticData.set(prefix + "index.html", {type: "text/html", content: INDEX_HTML});
|
staticData.set(prefix + "index.html", {
|
||||||
|
type: "text/html",
|
||||||
|
content: INDEX_HTML,
|
||||||
|
});
|
||||||
|
|
||||||
const sp = new URLSearchParams(self.location.search);
|
const sp = new URLSearchParams(self.location.search);
|
||||||
|
|
||||||
@@ -25,6 +27,3 @@ if (self.registration) {
|
|||||||
} else {
|
} else {
|
||||||
new WorkerLoader(self);
|
new WorkerLoader(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -1,11 +1,14 @@
|
|||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { register } from "register-service-worker";
|
import { register } from "register-service-worker";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
export class SWManager
|
export class SWManager {
|
||||||
{
|
constructor({
|
||||||
constructor({name = "sw.js", scope = "./", appName = "ReplayWeb.page", requireSubdomainIframe = false} = {}) {
|
name = "sw.js",
|
||||||
|
scope = "./",
|
||||||
|
appName = "ReplayWeb.page",
|
||||||
|
requireSubdomainIframe = false,
|
||||||
|
} = {}) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
@@ -37,7 +40,9 @@ export class SWManager
|
|||||||
console.error("Error during service worker registration:", error);
|
console.error("Error during service worker registration:", error);
|
||||||
this.errorMsg = this.getCrossOriginIframeMsg();
|
this.errorMsg = this.getCrossOriginIframeMsg();
|
||||||
if (!this.errorMsg) {
|
if (!this.errorMsg) {
|
||||||
this.errorMsg = `${this.appName} could not be loaded due to the following error:\n${error.toString()}`;
|
this.errorMsg = `${
|
||||||
|
this.appName
|
||||||
|
} could not be loaded due to the following error:\n${error.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
reject(this.errorMsg);
|
reject(this.errorMsg);
|
||||||
@@ -50,7 +55,9 @@ export class SWManager
|
|||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
|
|
||||||
error(errMsg) { handleError(errMsg); }
|
error(errMsg) {
|
||||||
|
handleError(errMsg);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
@@ -81,7 +88,6 @@ export class SWManager
|
|||||||
|
|
||||||
getSWErrorMsg() {
|
getSWErrorMsg() {
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
|
|
||||||
// must be loaded from a cross-origin (eg. subdomain)
|
// must be loaded from a cross-origin (eg. subdomain)
|
||||||
if (this.requireSubdomainIframe && !this.isCrossOriginIframe()) {
|
if (this.requireSubdomainIframe && !this.isCrossOriginIframe()) {
|
||||||
return `Sorry, due to security settings, this ${this.appName} embed only be viewed within a subdomain iframe.`;
|
return `Sorry, due to security settings, this ${this.appName} embed only be viewed within a subdomain iframe.`;
|
||||||
@@ -109,7 +115,6 @@ export class SWManager
|
|||||||
|
|
||||||
return `Sorry, ${this.appName} won't work in this browser as Service Workers are not supported in this window.
|
return `Sorry, ${this.appName} won't work in this browser as Service Workers are not supported in this window.
|
||||||
Please try a different browser.`;
|
Please try a different browser.`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderErrorReport(logo, override) {
|
renderErrorReport(logo, override) {
|
||||||
@@ -122,11 +127,16 @@ export class SWManager
|
|||||||
return html`
|
return html`
|
||||||
<section class="is-fullwidth">
|
<section class="is-fullwidth">
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<fa-icon style="margin: 1em;flex-grow: 1;" id="wrlogo" size="2.5rem" .svg=${logo} aria-hidden="true"></fa-icon>
|
<fa-icon
|
||||||
|
style="margin: 1em;flex-grow: 1;"
|
||||||
|
id="wrlogo"
|
||||||
|
size="2.5rem"
|
||||||
|
.svg=${logo}
|
||||||
|
aria-hidden="true"
|
||||||
|
></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
<div style="white-space: pre-wrap; text-align: center">${msg}</div>
|
<div style="white-space: pre-wrap; text-align: center">${msg}</div>
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,19 @@ import { Sorter } from "./sorter";
|
|||||||
import { URLResources } from "./url-resources";
|
import { URLResources } from "./url-resources";
|
||||||
import { Embed } from "./embed";
|
import { Embed } from "./embed";
|
||||||
|
|
||||||
export { ReplayWebApp, Chooser, CollIndex, CollInfo, Coll, Story, GDrive,
|
export {
|
||||||
Loader, Pages, PageEntry,
|
ReplayWebApp,
|
||||||
Replay, Sorter, URLResources, Embed };
|
Chooser,
|
||||||
|
CollIndex,
|
||||||
|
CollInfo,
|
||||||
|
Coll,
|
||||||
|
Story,
|
||||||
|
GDrive,
|
||||||
|
Loader,
|
||||||
|
Pages,
|
||||||
|
PageEntry,
|
||||||
|
Replay,
|
||||||
|
Sorter,
|
||||||
|
URLResources,
|
||||||
|
Embed,
|
||||||
|
};
|
||||||
|
|||||||
+140
-55
@@ -7,44 +7,49 @@ import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
|||||||
|
|
||||||
import "keyword-mark-element/lib/keyword-mark.js";
|
import "keyword-mark-element/lib/keyword-mark.js";
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class URLResources extends LitElement
|
class URLResources extends LitElement {
|
||||||
{
|
|
||||||
static get filters() {
|
static get filters() {
|
||||||
return [
|
return [
|
||||||
{ name: "HTML", filter: "text/html,text/xhtml" },
|
{ name: "HTML", filter: "text/html,text/xhtml" },
|
||||||
{ name: "Images", filter: "image/" },
|
{ name: "Images", filter: "image/" },
|
||||||
{ name: "Audio/Video", filter: "audio/,video/" },
|
{ name: "Audio/Video", filter: "audio/,video/" },
|
||||||
{ name: "PDF", filter: "application/pdf" },
|
{ name: "PDF", filter: "application/pdf" },
|
||||||
{name: "Javascript", filter: "application/javascript,application/x-javascript"},
|
{
|
||||||
|
name: "Javascript",
|
||||||
|
filter: "application/javascript,application/x-javascript",
|
||||||
|
},
|
||||||
{ name: "CSS", filter: "text/css" },
|
{ name: "CSS", filter: "text/css" },
|
||||||
{ name: "Fonts", filter: "font/,application/font-woff" },
|
{ name: "Fonts", filter: "font/,application/font-woff" },
|
||||||
{ name: "Plain Text", filter: "text/plain" },
|
{ name: "Plain Text", filter: "text/plain" },
|
||||||
{ name: "JSON", filter: "application/json" },
|
{ name: "JSON", filter: "application/json" },
|
||||||
{name: "DASH/HLS", filter: "application/dash+xml,application/x-mpegURL,application/vnd.apple.mpegurl"},
|
{
|
||||||
{name: "All URLs", filter: ""}
|
name: "DASH/HLS",
|
||||||
|
filter:
|
||||||
|
"application/dash+xml,application/x-mpegURL,application/vnd.apple.mpegurl",
|
||||||
|
},
|
||||||
|
{ name: "All URLs", filter: "" },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get sortKeys() {
|
static get sortKeys() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "url",
|
key: "url",
|
||||||
"name": "URL"
|
name: "URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "ts",
|
key: "ts",
|
||||||
"name": "Date"
|
name: "Date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "mime",
|
key: "mime",
|
||||||
"name": "Mime Type",
|
name: "Mime Type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "status",
|
key: "status",
|
||||||
"name": "Status"
|
name: "Status",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ class URLResources extends LitElement
|
|||||||
sortedResults: { type: Array },
|
sortedResults: { type: Array },
|
||||||
loading: { type: Boolean },
|
loading: { type: Boolean },
|
||||||
sortKey: { type: String },
|
sortKey: { type: String },
|
||||||
sortDesc: { type: Boolean }
|
sortDesc: { type: Boolean },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,18 +106,23 @@ class URLResources extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
if (changedProperties.has("query") ||
|
if (
|
||||||
|
changedProperties.has("query") ||
|
||||||
changedProperties.has("urlSearchType") ||
|
changedProperties.has("urlSearchType") ||
|
||||||
changedProperties.has("currMime")) {
|
changedProperties.has("currMime")
|
||||||
|
) {
|
||||||
this.doLoadResources();
|
this.doLoadResources();
|
||||||
const data = {
|
const data = {
|
||||||
query: this.query,
|
query: this.query,
|
||||||
urlSearchType: this.urlSearchType,
|
urlSearchType: this.urlSearchType,
|
||||||
currMime: this.currMime
|
currMime: this.currMime,
|
||||||
};
|
};
|
||||||
const replaceLoc = !changedProperties.has("currMime") && !changedProperties.has("urlSearchType");
|
const replaceLoc =
|
||||||
this.dispatchEvent(new CustomEvent("coll-tab-nav", {detail: {replaceLoc, data}}));
|
!changedProperties.has("currMime") &&
|
||||||
|
!changedProperties.has("urlSearchType");
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("coll-tab-nav", { detail: { replaceLoc, data } }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has("sortKey") || changedProperties.has("sortDesc")) {
|
if (changedProperties.has("sortKey") || changedProperties.has("sortDesc")) {
|
||||||
@@ -132,7 +142,7 @@ class URLResources extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let url = (this.urlSearchType !== "contains" ? this.query : "");
|
let url = this.urlSearchType !== "contains" ? this.query : "";
|
||||||
const prefix = url && this.urlSearchType === "prefix" ? 1 : 0;
|
const prefix = url && this.urlSearchType === "prefix" ? 1 : 0;
|
||||||
|
|
||||||
// optimization: if not starting with http, likely won't have a match here, so just add https://
|
// optimization: if not starting with http, likely won't have a match here, so just add https://
|
||||||
@@ -146,7 +156,7 @@ class URLResources extends LitElement
|
|||||||
mime,
|
mime,
|
||||||
url,
|
url,
|
||||||
prefix,
|
prefix,
|
||||||
count
|
count,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isMore) {
|
if (isMore) {
|
||||||
@@ -157,11 +167,13 @@ class URLResources extends LitElement
|
|||||||
params.set("fromTs", new Date(last.date).getTime());
|
params.set("fromTs", new Date(last.date).getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = await fetch(`${this.collInfo.apiPrefix}/urls?${params.toString()}`);
|
let resp = await fetch(
|
||||||
|
`${this.collInfo.apiPrefix}/urls?${params.toString()}`,
|
||||||
|
);
|
||||||
resp = await resp.json();
|
resp = await resp.json();
|
||||||
this.results = isMore ? this.results.concat(resp.urls) : resp.urls;
|
this.results = isMore ? this.results.concat(resp.urls) : resp.urls;
|
||||||
// can be more than count if multiple mimes
|
// can be more than count if multiple mimes
|
||||||
this.tryMore = (resp.urls.length >= count);
|
this.tryMore = resp.urls.length >= count;
|
||||||
this.filter();
|
this.filter();
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -185,7 +197,7 @@ class URLResources extends LitElement
|
|||||||
|
|
||||||
filter() {
|
filter() {
|
||||||
const filteredResults = [];
|
const filteredResults = [];
|
||||||
const filterText = (this.urlSearchType === "contains" ? this.query : "");
|
const filterText = this.urlSearchType === "contains" ? this.query : "";
|
||||||
for (const result of this.results) {
|
for (const result of this.results) {
|
||||||
if (!filterText || result.url.indexOf(filterText) >= 0) {
|
if (!filterText || result.url.indexOf(filterText) >= 0) {
|
||||||
filteredResults.push(result);
|
filteredResults.push(result);
|
||||||
@@ -197,7 +209,8 @@ class URLResources extends LitElement
|
|||||||
|
|
||||||
onScroll(event) {
|
onScroll(event) {
|
||||||
const element = event.currentTarget;
|
const element = event.currentTarget;
|
||||||
const diff = (element.scrollHeight - element.scrollTop) - element.clientHeight;
|
const diff =
|
||||||
|
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||||
if (this.tryMore && diff < 40) {
|
if (this.tryMore && diff < 40) {
|
||||||
this.doLoadResources(true);
|
this.doLoadResources(true);
|
||||||
}
|
}
|
||||||
@@ -220,7 +233,9 @@ class URLResources extends LitElement
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.sidebar) .level, :host(.sidebar) .level-left, :host(.sidebar) .level-right {
|
:host(.sidebar) .level,
|
||||||
|
:host(.sidebar) .level-left,
|
||||||
|
:host(.sidebar) .level-right {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,26 +315,40 @@ class URLResources extends LitElement
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div role="heading" aria-level="${this.isSidebar ? "2": "1"}" class="is-sr-only">URLs in ${this.collInfo.title}</div>
|
<div role="heading" aria-level="${
|
||||||
|
this.isSidebar ? "2" : "1"
|
||||||
|
}" class="is-sr-only">URLs in ${this.collInfo.title}</div>
|
||||||
|
|
||||||
<div role="heading" aria-level="${this.isSidebar ? "3": "2"}" class="is-sr-only">Search and Filter</div>
|
<div role="heading" aria-level="${
|
||||||
|
this.isSidebar ? "3" : "2"
|
||||||
|
}" class="is-sr-only">Search and Filter</div>
|
||||||
<div class="notification level is-marginless">
|
<div class="notification level is-marginless">
|
||||||
<div class="level-left flex-auto">
|
<div class="level-left flex-auto">
|
||||||
<div class="level-item flex-auto">
|
<div class="level-item flex-auto">
|
||||||
<span class="is-hidden-mobile">Search: </span>
|
<span class="is-hidden-mobile">Search: </span>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select @change="${this.onChangeTypeSearch}">
|
<select @change="${this.onChangeTypeSearch}">
|
||||||
${URLResources.filters.map((filter) => html`
|
${URLResources.filters.map(
|
||||||
<option value="${filter.filter}"
|
(filter) => html`
|
||||||
?selected="${filter.filter === this.currMime}">
|
<option
|
||||||
|
value="${filter.filter}"
|
||||||
|
?selected="${filter.filter === this.currMime}"
|
||||||
|
>
|
||||||
${filter.name}
|
${filter.name}
|
||||||
</option>
|
</option>
|
||||||
`)}
|
`,
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field flex-auto">
|
<div class="field flex-auto">
|
||||||
<div class="control has-icons-left ${this.loading ? "is-loading" : ""}">
|
<div class="control has-icons-left ${
|
||||||
<input type="text" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text" placeholder="Enter URL to Search">
|
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>
|
<span class="icon is-left"><fa-icon .svg="${fasSearch}"/></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -327,10 +356,18 @@ class URLResources extends LitElement
|
|||||||
</div>
|
</div>
|
||||||
<div class="control level-right">
|
<div class="control level-right">
|
||||||
<div style="margin-left: 1em" class="control">
|
<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}"> Contains</label>
|
<label class="radio has-text-left"><input type="radio" name="urltype" value="contains" ?checked="${
|
||||||
<label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === "prefix"}" @click="${this.onClickUrlType}"> Prefix</label>
|
this.urlSearchType === "contains"
|
||||||
<label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === "exact"}" @click="${this.onClickUrlType}"> Exact</label>
|
}" @click="${this.onClickUrlType}"> Contains</label>
|
||||||
<span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${this.filteredResults.length} Result(s)</span>
|
<label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${
|
||||||
|
this.urlSearchType === "prefix"
|
||||||
|
}" @click="${this.onClickUrlType}"> Prefix</label>
|
||||||
|
<label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${
|
||||||
|
this.urlSearchType === "exact"
|
||||||
|
}" @click="${this.onClickUrlType}"> Exact</label>
|
||||||
|
<span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${
|
||||||
|
this.filteredResults.length
|
||||||
|
} Result(s)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,30 +382,78 @@ class URLResources extends LitElement
|
|||||||
</wr-sorter>
|
</wr-sorter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div role="heading" aria-level="${this.isSidebar ? "3": "2"}" id="results-heading" class="is-sr-only">Results</div>
|
<div role="heading" aria-level="${
|
||||||
|
this.isSidebar ? "3" : "2"
|
||||||
|
}" id="results-heading" class="is-sr-only">Results</div>
|
||||||
|
|
||||||
<table class="all-results" aria-labelledby="results-heading num-results">
|
<table class="all-results" aria-labelledby="results-heading num-results">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="columns results-head has-text-weight-bold">
|
<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-url is-6 is-hidden-mobile"><a role="button" href="#" @click="${
|
||||||
<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>
|
this.onSort
|
||||||
<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>
|
}" @keyup="${clickOnSpacebarPress}" data-key="url" class="${
|
||||||
<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>
|
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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody class="main-scroll" @scroll="${this.onScroll}">
|
<tbody class="main-scroll" @scroll="${this.onScroll}">
|
||||||
${this.sortedResults.length ?
|
${
|
||||||
this.sortedResults.map((result) => html`
|
this.sortedResults.length
|
||||||
|
? this.sortedResults.map(
|
||||||
|
(result) => html`
|
||||||
<tr class="columns result">
|
<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)}">
|
<td class="column col-url is-6">
|
||||||
<keyword-mark keywords="${this.query}">${result.url}</keyword-mark>
|
<p class="minihead is-hidden-tablet">URL</p>
|
||||||
</a></td>
|
<a
|
||||||
<td class="column col-ts is-2"><p class="minihead is-hidden-tablet">Date</p>${new Date(result.date).toLocaleString()}</td>
|
@click="${this.onReplay}"
|
||||||
<td class="column col-mime is-3"><p class="minihead is-hidden-tablet">Mime Type</p>${result.mime}</td>
|
data-url="${result.url}"
|
||||||
<td class="column col-status is-1"><p class="minihead is-hidden-tablet">Status</p>${result.status}</td>
|
data-ts="${result.ts}"
|
||||||
|
href="${getReplayLink(
|
||||||
|
"resources",
|
||||||
|
result.url,
|
||||||
|
result.ts,
|
||||||
|
)}"
|
||||||
|
>
|
||||||
|
<keyword-mark keywords="${this.query}"
|
||||||
|
>${result.url}</keyword-mark
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="column col-ts is-2">
|
||||||
|
<p class="minihead is-hidden-tablet">Date</p>
|
||||||
|
${new Date(result.date).toLocaleString()}
|
||||||
|
</td>
|
||||||
|
<td class="column col-mime is-3">
|
||||||
|
<p class="minihead is-hidden-tablet">Mime Type</p>
|
||||||
|
${result.mime}
|
||||||
|
</td>
|
||||||
|
<td class="column col-status is-1">
|
||||||
|
<p class="minihead is-hidden-tablet">Status</p>
|
||||||
|
${result.status}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`) : html`<tr class="section"><td colspan="4"><i>No Results Found.</i></td></tr>`}
|
`,
|
||||||
|
)
|
||||||
|
: html`<tr class="section">
|
||||||
|
<td colspan="4"><i>No Results Found.</i></td>
|
||||||
|
</tr>`
|
||||||
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2209,6 +2209,11 @@ escape-string-regexp@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
|
eslint-config-prettier@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f"
|
||||||
|
integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==
|
||||||
|
|
||||||
eslint-scope@5.1.1:
|
eslint-scope@5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
@@ -4491,6 +4496,11 @@ prepend-http@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||||
|
|
||||||
|
prettier@^3.0.1:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
|
||||||
|
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
|
||||||
|
|
||||||
pretty-bytes@^5.6.0:
|
pretty-bytes@^5.6.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||||
|
|||||||
Reference in New Issue
Block a user