1.4.0 Release (#45)

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

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

* lint pass!

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

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

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

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

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

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

* bump version to 1.4.0
This commit is contained in:
Ilya Kreymer
2021-04-25 16:45:40 -07:00
committed by GitHub
parent 36ae1aefda
commit 0ef8f955db
29 changed files with 832 additions and 663 deletions
+33
View File
@@ -0,0 +1,33 @@
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-restricted-globals": [
2,
"event", "error"
],
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
}
};
+17
View File
@@ -1,5 +1,22 @@
## CHANGES ## CHANGES
v1.4.0
- Embed: add 'noSandbox' option to not add sandbox to embed iframe
- UI: Double-clicking on page entry causes reload.
- UI: Fix location bar enter not always reloading specified URL
- UI: Fix 'Purge and Reload' to reload current page
- UI: Fix initial load not properly waiting for service worker
- UI: Show 'Loading Archives...' on initial load, until first API response succeeds, instead of 'No Archives'
- Fidelity: Improved fuzzy matching for dynamic sites
- Fidelity: Fix for multipart/form-data replay dropping certain query params
- Fidelity: don't remove spaces in css urls
- Fidelity: Better detection of JSON responses served in response to 'Accept: application/json' but with wrong content-type
- Docs: copy package.json to 'data' to allow access to version in UI, use latest fixed version in Collec
- API: Switch from /wabac/ -> /w/ for replay paths, make path configurable
- Extensibility: Make file chooser, collection list more extensible/stylable (for use with archiveweb.page)
- Lint pass on code base
- Dependencies: wabac 2.7.0, bulma 0.9.2, electron 11.4.3
v1.3.15 v1.3.15
- Fidelity: fix encoding issues with UTF-8 encoded JS/CSS introduced in 1.3.13 (fixed via wabac 2.6.9) - Fidelity: fix encoding issues with UTF-8 encoded JS/CSS introduced in 1.3.13 (fixed via wabac 2.6.9)
- Dependencies: wabac 2.6.9 - Dependencies: wabac 2.6.9
-4
View File
@@ -22,10 +22,6 @@ logo: "/assets/logo.svg"
plugins: plugins:
- jekyll-remote-theme - jekyll-remote-theme
version:
- 1.3.12
#baseUrl: '/' #baseUrl: '/'
exclude: exclude:
+6 -5
View File
@@ -23,7 +23,7 @@ the following snippet to your HTML page:
my-web-archive-embed.html my-web-archive-embed.html
```html ```html
<script src="https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.version }}/ui.js"></script> <script src="https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.data.package.version }}/ui.js"></script>
<replay-web-page source="s3://webrecorder-builds/warcs/netpreserve-twitter.warc" <replay-web-page source="s3://webrecorder-builds/warcs/netpreserve-twitter.warc"
url="https://twitter.com/netpreserve"></replay-web-page> url="https://twitter.com/netpreserve"></replay-web-page>
``` ```
@@ -39,7 +39,7 @@ one-line script
./replay/sw.js ./replay/sw.js
```javascript ```javascript
importScripts("https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.version }}/sw.js"); importScripts("https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.data.package.version }}/sw.js");
``` ```
Thus, if the HTML snippet was added to `https://my-site.example.com/path/my-web-archive-embed.html` Thus, if the HTML snippet was added to `https://my-site.example.com/path/my-web-archive-embed.html`
@@ -71,16 +71,17 @@ The `<replay-web-page>` tag is a web component and supports a number of addition
| `replayBase` | Location of the `sw.js` file, defaults to `./replay/` as mentioned above, but can be overridden. | | `replayBase` | Location of the `sw.js` file, defaults to `./replay/` as mentioned above, but can be overridden. |
| `coll` | Internal id for this collection, usually generated automatically. | `coll` | Internal id for this collection, usually generated automatically.
| `config` | Extra per collection config options (such as custom fuzzy matching rules, TODO add more info!) | | `config` | Extra per collection config options (such as custom fuzzy matching rules, TODO add more info!) |
| `noSandbox` | (`true` / `false`) if set to true, don't wrap iframe in `sandbox`. Used as extra precaution to avoid escaping iframe, but prevents PDFs from loading in embed archive. Set for archives of known/trusted sites. |
### Versioning ### Versioning
Note that the above example uses the paths as, which is a CDN for Javascript packages at a specific version: Note that the above example uses the paths as, which is a CDN for Javascript packages at a specific version:
- `https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.version }}/ui.js` - `https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.data.package.version }}/ui.js`
- `https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.version }}/sw.js` - `https://cdn.jsdelivr.net/npm/replaywebpage@{{ site.data.package.version }}/sw.js`
These URLs point to a specific version of ReplayWeb.page software released on NPM, eg. `{{ site.version }}`, meaning that your replay should stay stable, even if ReplayWeb.page is updated. These URLs point to a specific version of ReplayWeb.page software released on NPM, eg. `{{ site.data.package.version }}`, meaning that your replay should stay stable, even if ReplayWeb.page is updated.
Although ReplayWeb.page strives to remain backwards compatible, this addresses any potential issue of older embeds breaking when the replay system is updated. Although ReplayWeb.page strives to remain backwards compatible, this addresses any potential issue of older embeds breaking when the replay system is updated.
+6 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "replaywebpage", "name": "replaywebpage",
"productName": "ReplayWeb.page", "productName": "ReplayWeb.page",
"version": "1.3.15", "version": "1.4.0",
"description": "Serverless Web Archive Replay", "description": "Serverless Web Archive Replay",
"repository": "https://github.com/webrecorder/replayweb.page", "repository": "https://github.com/webrecorder/replayweb.page",
"homepage": "https://replayweb.page/", "homepage": "https://replayweb.page/",
@@ -9,8 +9,8 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.0",
"@webrecorder/wabac": "^2.6.9", "@webrecorder/wabac": "^2.7.0",
"bulma": "^0.9.0", "bulma": "^0.9.2",
"electron-log": "^4.3.0", "electron-log": "^4.3.0",
"electron-updater": "^4.3.5", "electron-updater": "^4.3.5",
"fetch-ndjson": "^1.1.0", "fetch-ndjson": "^1.1.0",
@@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"copy-webpack-plugin": "^6.3.0", "copy-webpack-plugin": "^6.3.0",
"css-loader": "^3.5.3", "css-loader": "^3.5.3",
"electron": "^11.3.0", "electron": "^11.4.3",
"electron-builder": "^22.7.0", "electron-builder": "^22.7.0",
"electron-notarize": "^1.0.0", "electron-notarize": "^1.0.0",
"http-server": "^0.12.3", "http-server": "^0.12.3",
@@ -62,7 +62,8 @@
"start-jekyll": "yarn run build-docs && bundle exec jekyll serve", "start-jekyll": "yarn run build-docs && bundle exec jekyll serve",
"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"
}, },
"build": { "build": {
"afterSign": "build/notarize.js", "afterSign": "build/notarize.js",
+34 -30
View File
@@ -1,11 +1,11 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc'; import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress } from "./misc";
import { registerSW } from './pageutils'; import { registerSW } from "./pageutils";
import fasHelp from '@fortawesome/fontawesome-free/svgs/solid/question-circle.svg'; import 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";
// =========================================================================== // ===========================================================================
@@ -16,7 +16,7 @@ class ReplayWebApp extends LitElement
this.sourceUrl = null; this.sourceUrl = null;
this.collTitle = null; this.collTitle = null;
this.showAbout = false; this.showAbout = false;
this.pageParams = {}; this.pageParams = new URLSearchParams();
this.inited = false; this.inited = false;
this.navMenuShown = false; this.navMenuShown = false;
@@ -25,6 +25,9 @@ class ReplayWebApp extends LitElement
this.pageTitle = ""; this.pageTitle = "";
this.pageReplay = false; this.pageReplay = false;
this.loadInfo = null;
// eslint-disable-next-line no-undef
registerSW(__SW_NAME__); registerSW(__SW_NAME__);
this.safariKeyframes(); this.safariKeyframes();
@@ -52,7 +55,7 @@ class ReplayWebApp extends LitElement
pageTitle: { type: String }, pageTitle: { type: String },
pageReplay: { type: Boolean }, pageReplay: { type: Boolean },
source: { type: String } source: { type: String }
} };
} }
static get styles() { static get styles() {
@@ -168,30 +171,30 @@ class ReplayWebApp extends LitElement
<span class="no-wrap is-size-6">&nbsp;&nbsp;/&nbsp; <span class="no-wrap is-size-6">&nbsp;&nbsp;/&nbsp;
${this.pageReplay ? html`<i>${this.pageTitle}</i>` : this.pageTitle} ${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 href="#" role="button" id="menu-button" @click="${this.onNavMenu}" @keyup="${clickOnSpacebarPress}"
class="navbar-burger burger ${this.navMenuShown ? 'is-active' : ''}" aria-label="main menu" aria-haspopup="true" aria-expanded="${this.navMenuShown}"> class="navbar-burger burger ${this.navMenuShown ? "is-active" : ""}" aria-label="main menu" aria-haspopup="true" aria-expanded="${this.navMenuShown}">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
</div> </div>
${!this.sourceUrl ? html` ${!this.sourceUrl ? html`
<div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}"> <div class="navbar-menu ${this.navMenuShown ? "is-active" : ""}">
<div class="navbar-start"> <div class="navbar-start">
${IS_APP ? html` ${IS_APP ? html`
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}" @keyup="${clickOnSpacebarPress}"> <a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${() => window.history.back()}" @keyup="${clickOnSpacebarPress}">
<fa-icon size="1.0rem" .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Back</span> <fa-icon size="1.0rem" .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Back</span>
</a> </a>
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}" @keyup="${clickOnSpacebarPress}"> <a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${() => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
<fa-icon size="1.0rem" .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Forward</span> <fa-icon size="1.0rem" .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Forward</span>
</a> </a>
` : ``} ` : ""}
</div> </div>
${!this.embed ? html` ${!this.embed ? html`
<div class="navbar-end"> <div class="navbar-end">
@@ -211,8 +214,8 @@ class ReplayWebApp extends LitElement
-- NB: the About modal is currently inaccessible to people using keyboards or screen readers. -- 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() {
@@ -225,7 +228,7 @@ 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=${(e) => this.showAbout = true}></wr-coll>`; @about-show=${() => this.showAbout = true}></wr-coll>`;
} }
renderHomeIndex() { renderHomeIndex() {
@@ -233,7 +236,7 @@ class ReplayWebApp extends LitElement
<wr-coll-index> <wr-coll-index>
${!IS_APP ? html` ${!IS_APP ? html`
<p slot="header" class="tagline is-size-5 has-text-centered">Explore and Replay Interactive Archived Webpages Directly in your Browser. <i><a target="_blank" href="./docs/examples">(See Examples)</a></i></p> <p slot="header" class="tagline is-size-5 has-text-centered">Explore and Replay Interactive Archived Webpages Directly in your Browser. <i><a target="_blank" href="./docs/examples">(See Examples)</a></i></p>
` : ``} ` : ""}
<wr-chooser slot="header" @load-start=${this.onStartLoad}></wr-chooser> <wr-chooser slot="header" @load-start=${this.onStartLoad}></wr-chooser>
</wr-coll-index>`; </wr-coll-index>`;
} }
@@ -244,17 +247,17 @@ 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() : ""}
`; `;
} }
firstUpdated() { firstUpdated() {
this.initRoute(); this.initRoute();
window.addEventListener("popstate", (event) => { window.addEventListener("popstate", () => {
this.initRoute(); this.initRoute();
}); });
} }
@@ -266,7 +269,7 @@ class ReplayWebApp extends LitElement
} }
onFavIcons(event) { onFavIcons(event) {
const head = document.querySelector('head'); const head = document.querySelector("head");
const oldLinks = document.querySelectorAll("link[rel*='icon']"); const oldLinks = document.querySelectorAll("link[rel*='icon']");
for (const link of oldLinks) { for (const link of oldLinks) {
@@ -274,7 +277,7 @@ class ReplayWebApp extends LitElement
} }
for (const icon of event.detail.icons) { for (const icon of event.detail.icons) {
const link = document.createElement('link'); const link = document.createElement("link");
link.rel = icon.rel; link.rel = icon.rel;
link.href = icon.href; link.href = icon.href;
head.appendChild(link); head.appendChild(link);
@@ -285,7 +288,7 @@ class ReplayWebApp extends LitElement
// This is a workaround, since this app's routing doesn't permit normal // This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors. // following of in-page anchors.
event.preventDefault(); event.preventDefault();
this.renderRoot.querySelector("#skip-main-target").focus() this.renderRoot.querySelector("#skip-main-target").focus();
} }
onNavMenu(event) { onNavMenu(event) {
@@ -348,7 +351,7 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("config")) { if (this.pageParams.get("config")) {
if (!this.loadInfo) { if (!this.loadInfo) {
this.loadInfo = {} this.loadInfo = {};
} }
try { try {
this.loadInfo.extraConfig = JSON.parse(this.pageParams.get("config")); this.loadInfo.extraConfig = JSON.parse(this.pageParams.get("config"));
@@ -359,7 +362,7 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("basePageUrl")) { if (this.pageParams.get("basePageUrl")) {
if (!this.loadInfo) { if (!this.loadInfo) {
this.loadInfo = {extraConfig: {}} this.loadInfo = {extraConfig: {}};
} }
if (!this.loadInfo.extraConfig) { if (!this.loadInfo.extraConfig) {
this.loadInfo.extraConfig = {}; this.loadInfo.extraConfig = {};
@@ -369,18 +372,19 @@ class ReplayWebApp extends LitElement
if (this.pageParams.get("customColl")) { if (this.pageParams.get("customColl")) {
if (!this.loadInfo) { if (!this.loadInfo) {
this.loadInfo = {} this.loadInfo = {};
} }
this.loadInfo.customColl = this.pageParams.get("customColl"); this.loadInfo.customColl = this.pageParams.get("customColl");
} }
if (IS_APP && this.sourceUrl.startsWith("file://")) { if (IS_APP && this.sourceUrl.startsWith("file://")) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__); const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("filename", this.sourceUrl.slice("file://".length)); url.searchParams.set("filename", this.sourceUrl.slice("file://".length));
this.loadInfo = { this.loadInfo = {
sourceUrl: this.sourceUrl, sourceUrl: this.sourceUrl,
loadUrl: url.href, loadUrl: url.href,
} };
} }
} }
@@ -456,7 +460,7 @@ 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 ${IS_APP ? "App" : ""}</p>
<button class="delete" aria-label="close" @click="${this.onAboutClose}"></button> <button class="delete" aria-label="close" @click="${this.onAboutClose}"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
@@ -465,7 +469,7 @@ 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` ${IS_APP ? html`
+39 -22
View File
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { IS_APP, wrapCss } from './misc'; import { IS_APP, wrapCss } from "./misc";
import fasUpload from '@fortawesome/fontawesome-free/svgs/solid/upload.svg'; import fasUpload from "@fortawesome/fontawesome-free/svgs/solid/upload.svg";
// =========================================================================== // ===========================================================================
@@ -14,12 +14,18 @@ class Chooser extends LitElement
this.file = null; this.file = null;
this.hasNativeFS = !!window.showOpenFilePicker && !IS_APP; this.hasNativeFS = !!window.showOpenFilePicker && !IS_APP;
this.newFullImport = false;
this.noHead = false;
} }
static get properties() { static get properties() {
return { return {
fileDisplayName: { type: String } fileDisplayName: { type: String },
} newFullImport: { type: Boolean },
noHead: { type: Boolean }
};
} }
onChooseFile(event) { onChooseFile(event) {
@@ -40,16 +46,17 @@ class Chooser extends LitElement
const options = { const options = {
types: [ types: [
{ {
description: 'WARC, WACZ, HAR and WBN Files', description: "WARC, WACZ, HAR and WBN Files",
accept: { accept: {
'application/warc': ['.warc', '.gz'], "application/warc": [".warc", ".gz"],
'application/har': ['.har'], "application/har": [".har"],
'application/wacz': ['.wacz'], "application/wacz": [".wacz"],
'application/wbn': ['.wbn'] "application/wbn": [".wbn"],
"application/json": [".json"],
} }
} }
] ]
} };
const [fileHandle] = await window.showOpenFilePicker(options); const [fileHandle] = await window.showOpenFilePicker(options);
this.fileHandle = fileHandle; this.fileHandle = fileHandle;
@@ -71,12 +78,13 @@ class Chooser extends LitElement
loadInfo.isFile = true; loadInfo.isFile = true;
// file.path only available in electron app // file.path only available in electron app
if (this.file.path) { if (this.file.path) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__); const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("filename", this.file.path); url.searchParams.set("filename", this.file.path);
loadInfo.loadUrl = url.href; loadInfo.loadUrl = url.href;
loadInfo.noCache = true; loadInfo.noCache = true;
} else if (this.fileHandle) { } else if (this.fileHandle) {
loadInfo.loadUrl = this.fileDisplayName + "-" + this.randomId(); loadInfo.loadUrl = this.fileDisplayName;
loadInfo.extra = {fileHandle: this.fileHandle}; loadInfo.extra = {fileHandle: this.fileHandle};
loadInfo.noCache = false; loadInfo.noCache = false;
} else { } else {
@@ -88,6 +96,8 @@ class Chooser extends LitElement
loadInfo.name = this.fileDisplayName; loadInfo.name = this.fileDisplayName;
} }
loadInfo.newFullImport = this.newFullImport;
this.dispatchEvent(new CustomEvent("load-start", {detail: loadInfo})); this.dispatchEvent(new CustomEvent("load-start", {detail: loadInfo}));
return false; return false;
@@ -119,11 +129,18 @@ class Chooser extends LitElement
} }
.panel-heading { .panel-heading {
background-color: #cff3ff; background-color: #cff3ff;
}
.message-header {
background-color: #cff3ff;
color: black;
}
.heading-size {
font-size: 0.85rem; font-size: 0.85rem;
} }
form { form {
flex-grow: 1; flex-grow: 1;
flex-shrink: 0; flex-shrink: 0;
margin-bottom: 0;
} }
p.control.is-expanded { p.control.is-expanded {
width: min-content; width: min-content;
@@ -149,20 +166,20 @@ class Chooser extends LitElement
} }
} }
`); `);
}; }
render() { render() {
return html` return html`
<section class="section less-padding"> <section class="section ${this.noHead ? "is-paddingless" : "less-padding"}">
<div class="panel"> <div class="${this.noHead ? "" : "panel"}">
<h4 class="panel-heading">Load Web Archive</h4> <div class="${this.noHead ? "is-hidden" : "panel-heading"} heading-size">${this.newFullImport ? "Import Existing" : "Load"} Web Archive</div>
<div class="extra-padding panel-block file has-name"> <div class="${this.noHead ? "" : "panel-body extra-padding"} file has-name ${this.noHead ? "is-small" : ""}">
<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 ? html`
<input class="file-input" <input class="file-input"
@click="${(e) => e.currentTarget.value = null}" @click="${(e) => e.currentTarget.value = null}"
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ``} @change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ""}
<span class="file-cta" @click="${this.onChooseNativeFile}"> <span class="file-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 size="0.9em" .svg=${fasUpload} aria-hidden="true"></fa-icon>
@@ -175,16 +192,16 @@ class Chooser extends LitElement
<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 class="file-name input ${this.noHead ? "is-small" : ""}" type="text"
name="filename" id="filename" name="filename" id="filename"
pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn))|(googledrive:\/\/.+)" pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn|json))|(googledrive:\/\/.+)"
.value="${this.fileDisplayName}" .value="${this.fileDisplayName}"
@input="${this.onInput}" @input="${this.onInput}"
autocomplete="off" autocomplete="off"
placeholder="Enter a URL or click 'Choose File' to select a WARC, WACZ, HAR or WBN archive source"> placeholder="${this.newFullImport ? "Click 'Choose File' to select a local archive to import" : "Enter a URL or click 'Choose File' to select a WARC, WACZ, HAR or WBN archive source"}">
</p> </p>
<div class="control"> <div class="control">
<button type="submit" class="button is-hidden-mobile is-primary">Load</button> <button type="submit" class="button is-hidden-mobile is-primary ${this.noHead ? "is-small" : ""}">${this.newFullImport ? "Import" : "Load"}</button>
</div> </div>
</div> </div>
+66 -35
View File
@@ -1,14 +1,14 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss } from './misc'; import { wrapCss, apiPrefix } from "./misc";
import prettyBytes from 'pretty-bytes'; import prettyBytes from "pretty-bytes";
import fasCopy from '@fortawesome/fontawesome-free/svgs/regular/copy.svg'; import fasCopy from "@fortawesome/fontawesome-free/svgs/regular/copy.svg";
import fasArrowUp from '@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg'; import fasArrowUp from "@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg";
import fasArrowDown from '@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg'; import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg'; import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
// =========================================================================== // ===========================================================================
@@ -19,7 +19,7 @@ class CollIndex extends LitElement
this.colls = []; this.colls = [];
this.filteredColls = []; this.filteredColls = [];
this.sortedColls = []; this.sortedColls = null;
this.query = ""; this.query = "";
@@ -29,6 +29,9 @@ class CollIndex extends LitElement
this.dateName = "Date Loaded"; this.dateName = "Date Loaded";
this.headerName = "Loaded Archives"; this.headerName = "Loaded Archives";
this.typeFilter = "";
this.indexParams = "";
} }
get sortKeys() { get sortKeys() {
@@ -62,8 +65,8 @@ class CollIndex extends LitElement
_deleting: { type: Object }, _deleting: { type: Object },
dateName: { type: String }, dateName: { type: String },
headerName: { type: String } headerName: { type: String },
} };
} }
firstUpdated() { firstUpdated() {
@@ -90,6 +93,7 @@ class CollIndex extends LitElement
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.title && coll.title.indexOf(this.query) >= 0)) { (coll.title && coll.title.indexOf(this.query) >= 0)) {
this.filteredColls.push(coll); this.filteredColls.push(coll);
} }
@@ -97,18 +101,23 @@ class CollIndex extends LitElement
} }
async loadColls() { async loadColls() {
const resp = await fetch("./wabac/api/index"); const resp = await fetch(`${apiPrefix}/coll-index?${this.indexParams}`);
try { try {
if (resp.status !== 200) {
throw new Error("Invalid API Response");
}
const json = await resp.json(); const json = await resp.json();
this.colls = json.colls.map((coll) => { this.colls = json.colls.map((coll) => {
coll.title = coll.title || coll.filename; coll.title = coll.title || coll.filename;
return coll; return coll;
}); });
} catch (e) {
// likely no sw registered yet
}
this._deleting = {}; this._deleting = {};
this.sortedColls = [];
} catch (e) {
// likely no sw registered yet, or waiting for new sw to register
}
} }
async onDeleteColl(event) { async onDeleteColl(event) {
@@ -130,7 +139,7 @@ class CollIndex extends LitElement
this._deleting[coll.sourceUrl] = true; this._deleting[coll.sourceUrl] = true;
this.requestUpdate(); this.requestUpdate();
const resp = await fetch(`./wabac/api/${coll.id}`, {method: 'DELETE'}); const resp = await fetch(`${apiPrefix}/c/${coll.id}`, {method: "DELETE"});
if (resp.status === 200) { if (resp.status === 200) {
const json = await resp.json(); const json = await resp.json();
this.colls = json.colls; this.colls = json.colls;
@@ -139,7 +148,11 @@ class CollIndex extends LitElement
} }
static get styles() { static get styles() {
return wrapCss(css` return wrapCss(CollIndex.compStyles);
}
static get compStyles() {
return css`
:host { :host {
overflow-y: auto; overflow-y: auto;
min-width: 0; min-width: 0;
@@ -170,6 +183,11 @@ class CollIndex extends LitElement
fa-icon { fa-icon {
vertical-align: middle; vertical-align: middle;
} }
.panel-color {
background-color: rgb(210, 249, 214);
}
.copy { .copy {
color: black; color: black;
margin: 0px; margin: 0px;
@@ -207,7 +225,8 @@ class CollIndex extends LitElement
.coll-block { .coll-block {
position: relative; position: relative;
} }
.delete { .delete-button {
width: 32px;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
@@ -229,31 +248,38 @@ class CollIndex extends LitElement
height: 269px; height: 269px;
margin-top: -269px; margin-top: -269px;
} }
`); `;
}
renderHeader() {
return html`<h2 class="panel-heading panel-color"><span>${this.headerName}</span></h2>`;
}
renderSearchHeader() {
return "";
} }
render() { render() {
const hasHeader = this.childElementCount > 0; const hasHeader = this.childElementCount > 0;
return html` return html`
<header class="${this.hideHeader ? 'closed' : ''}"> <header class="${this.hideHeader ? "closed" : ""}">
<slot name="header"></slot> <slot name="header"></slot>
</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 ? html`
<button @click=${(e) => this.hideHeader = !this.hideHeader} class="collapse button is-small"> <button @click=${() => this.hideHeader = !this.hideHeader} class="collapse button is-small">
<span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span> <span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span>
<span>${this.hideHeader ? 'Show ' : 'Hide'} <span class="is-sr-only">Header</span></span> <span>${this.hideHeader ? "Show " : "Hide"} <span class="is-sr-only">Header</span></span>
</button>` : ``} </button>` : ""}
</div> </div>
<div class="panel is-light"> <div class="panel">
<h2 class="panel-heading"><span>${this.headerName}</span> ${this.renderHeader()}
</h2>
${this.colls.length ? html` ${this.colls.length ? html`
<div class="panel-block sort-header is-small"> <div class="panel-block sort-header is-small">
<div class="control has-icons-left"> ${this.renderSearchHeader()}
<div class="control has-icons-left has-addons">
<input type="text" class="input is-small" @input="${(e) => this.query = e.currentTarget.value}" .value="${this.query}" type="text" <input type="text" class="input is-small" @input="${(e) => this.query = e.currentTarget.value}" .value="${this.query}" type="text"
placeholder="Search by Archive Title or Source"> placeholder="Search by Archive Title or Source">
<span class="icon is-left is-small"><fa-icon .svg="${fasSearch}"/></span> <span class="icon is-left is-small"><fa-icon .svg="${fasSearch}"/></span>
@@ -268,13 +294,13 @@ class CollIndex extends LitElement
</div> </div>
<div class="coll-list"> <div class="coll-list">
${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] ? html`
<button class="delete" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button> <button class="delete delete-button" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
` : html` ` : html`
<span class="button delete is-loading is-static">Deleting</span`} <span class="button delete-button is-loading is-static">Deleting</span`}
</div> </div>
`)} `)}
</div> </div>
@@ -282,7 +308,8 @@ class CollIndex extends LitElement
` : html` ` : html`
<div class="panel-block extra-padding"> <div class="panel-block extra-padding">
${this.renderEmpty()} ${this.sortedColls === null ? html`<i>Loading Archives...</i>` :
this.renderEmpty()}
</div> </div>
`} `}
</div> </div>
@@ -314,11 +341,15 @@ class CollInfo extends LitElement
coll: { type: Object }, coll: { type: Object },
detailed: { type: Boolean }, detailed: { type: Boolean },
canDelete: { type: Boolean } canDelete: { type: Boolean }
} };
} }
static get styles() { static get styles() {
return wrapCss(css` return wrapCss(CollInfo.compStyles);
}
static get compStyles() {
return css`
.columns { .columns {
width: 100%; width: 100%;
} }
@@ -369,7 +400,7 @@ class CollInfo extends LitElement
font-size: 10px; font-size: 10px;
font-weight: bold; font-weight: bold;
} }
`); `;
} }
render() { render() {
@@ -396,7 +427,7 @@ class CollInfo extends LitElement
<span class="source-text"><p class="minihead">Source</p>${coll.sourceUrl}&nbsp;</span> <span class="source-text"><p class="minihead">Source</p>${coll.sourceUrl}&nbsp;</span>
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a> <a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a>
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html` ${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`
<p><i>(${coll.filename})</i></p>` : ''} <p><i>(${coll.filename})</i></p>` : ""}
</div> </div>
${detailed ? html` ${detailed ? html`
<div class="column"><p class="minihead">Filename</p>${coll.filename}</div>` : html``} <div class="column"><p class="minihead">Filename</p>${coll.filename}</div>` : html``}
+94 -84
View File
@@ -1,28 +1,28 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc'; import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress, apiPrefix, replayPrefix } from "./misc";
import { sourceToId, tsToDate, getPageDateTS } from './pageutils'; import { sourceToId, tsToDate, getPageDateTS } from "./pageutils";
import fasBook from '@fortawesome/fontawesome-free/svgs/solid/book.svg'; import fasBook from "@fortawesome/fontawesome-free/svgs/solid/book.svg";
import farListAlt from '@fortawesome/fontawesome-free/svgs/regular/list-alt.svg'; import farListAlt from "@fortawesome/fontawesome-free/svgs/regular/list-alt.svg";
import farResources from '@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg'; import farResources from "@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg";
import farPages from '@fortawesome/fontawesome-free/svgs/regular/file-image.svg'; import farPages from "@fortawesome/fontawesome-free/svgs/regular/file-image.svg";
import fasInfoIcon from '@fortawesome/fontawesome-free/svgs/solid/info-circle.svg'; import fasInfoIcon from "@fortawesome/fontawesome-free/svgs/solid/info-circle.svg";
import fasSync from '@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg'; import fasSync from "@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg";
import fasRefresh from '@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg'; import fasRefresh from "@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg";
import fasFullscreen from '@fortawesome/fontawesome-free/svgs/solid/desktop.svg'; import fasFullscreen from "@fortawesome/fontawesome-free/svgs/solid/desktop.svg";
import fasUnfullscreen from '@fortawesome/fontawesome-free/svgs/solid/compress-arrows-alt.svg'; import fasUnfullscreen from "@fortawesome/fontawesome-free/svgs/solid/compress-arrows-alt.svg";
import fasLeft from '@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg'; import fasLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg";
import fasRight from '@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg'; import fasRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg";
import fasMenuV from '@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg'; import fasMenuV from "@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg";
import fasAngleLeft from '@fortawesome/fontawesome-free/svgs/solid/angle-left.svg'; import fasAngleLeft from "@fortawesome/fontawesome-free/svgs/solid/angle-left.svg";
import fasAngleRight from '@fortawesome/fontawesome-free/svgs/solid/angle-right.svg'; import fasAngleRight from "@fortawesome/fontawesome-free/svgs/solid/angle-right.svg";
import Split from 'split.js'; import Split from "split.js";
const RWP_SCHEME = "search://"; const RWP_SCHEME = "search://";
@@ -37,11 +37,6 @@ class Coll extends LitElement
this.inited = false; this.inited = false;
this.isLoading = false; this.isLoading = false;
this.baseApiPrefix = "./wabac/api";
this.baseReplayPrefix = "./wabac";
this.apiPrefix = "";
this.replayPrefix = "";
this.coll = ""; this.coll = "";
this.collInfo = null; this.collInfo = null;
@@ -61,7 +56,7 @@ class Coll extends LitElement
"story": "Story", "story": "Story",
"resources": "URLs", "resources": "URLs",
"info": "Archive Info", "info": "Archive Info",
} };
this.menuActive = false; this.menuActive = false;
@@ -69,7 +64,7 @@ class Coll extends LitElement
this.editable = false; this.editable = false;
this.showSidebar = localStorage.getItem(`pages:showSidebar`) === "1"; this.showSidebar = localStorage.getItem("pages:showSidebar") === "1";
this.splitter = null; this.splitter = null;
this.isVisible = true; this.isVisible = true;
@@ -112,19 +107,19 @@ class Coll extends LitElement
appName: { type: String }, appName: { type: String },
appLogo: { type: String }, appLogo: { type: String },
} };
} }
firstUpdated() { firstUpdated() {
this.inited = true; this.inited = true;
window.addEventListener("hashchange", (event) => this.onHashChange(event)); window.addEventListener("hashchange", (event) => this.onHashChange(event));
this.addEventListener("fullscreenchange", (event) => { this.addEventListener("fullscreenchange", () => {
this.isFullscreen = !!document.fullscreenElement; this.isFullscreen = !!document.fullscreenElement;
}); });
if (this.embed) { if (this.embed) {
this.observer = new IntersectionObserver((entries, observer) => { this.observer = new IntersectionObserver((entries/*, observer*/) => {
this.isVisible = entries[0].isIntersecting; this.isVisible = entries[0].isIntersecting;
}); });
@@ -182,14 +177,14 @@ class Coll extends LitElement
} }
} }
if (this.embed && window.parent !== window) { if (this.embed && window.parent !== window) {
window.parent.postMessage(this.tabData, '*'); window.parent.postMessage(this.tabData, "*");
} }
} }
this._locUpdateNeeded = false; this._locUpdateNeeded = false;
} }
if (changedProperties.has("showSidebar")) { if (changedProperties.has("showSidebar")) {
if (!this.embed) { if (!this.embed) {
localStorage.setItem(`pages:showSidebar`, this.showSidebar ? "1" : "0"); localStorage.setItem("pages:showSidebar", this.showSidebar ? "1" : "0");
} }
} }
@@ -218,14 +213,16 @@ class Coll extends LitElement
onDragEnd() { onDragEnd() {
replay.setDisablePointer(false); replay.setDisablePointer(false);
} }
} };
this.splitter = Split([contents, replay], opts); this.splitter = Split([contents, replay], opts);
} }
} else if (this.splitter) { } else if (this.splitter) {
try { try {
this.splitter.destroy(); this.splitter.destroy();
} catch (e) {} } catch (e) {
// ignore, remove splitter
}
this.splitter = null; this.splitter = null;
} }
} }
@@ -246,10 +243,10 @@ class Coll extends LitElement
this.coll = coll; this.coll = coll;
const apiPrefix = this.baseApiPrefix + "/" + coll; const collApiPrefix = apiPrefix + "/c/" + coll;
const replayPrefix = this.baseReplayPrefix + "/" + coll; const collReplayPrefix = replayPrefix + "/" + coll;
const resp = await fetch(apiPrefix + "?all=1"); const resp = await fetch(collApiPrefix + "?all=1");
if (resp.status != 200) { if (resp.status != 200) {
this.collInfo = {}; this.collInfo = {};
@@ -258,7 +255,12 @@ class Coll extends LitElement
const json = await resp.json(); const json = await resp.json();
this.collInfo = {apiPrefix, replayPrefix, coll, ...json}; this.collInfo = {
apiPrefix: collApiPrefix,
replayPrefix: collReplayPrefix,
coll,
...json
};
if (!this.collInfo.title) { if (!this.collInfo.title) {
this.collInfo.title = this.collInfo.filename; this.collInfo.title = this.collInfo.filename;
@@ -281,6 +283,9 @@ class Coll extends LitElement
onCollLoaded(event) { onCollLoaded(event) {
this.doUpdateInfo(); this.doUpdateInfo();
this.loadInfo = null; this.loadInfo = null;
if (event.detail.sourceUrl) {
this.sourceUrl = event.detail.sourceUrl;
}
this.dispatchEvent(new CustomEvent("coll-loaded", {detail: { this.dispatchEvent(new CustomEvent("coll-loaded", {detail: {
sourceUrl: this.sourceUrl, sourceUrl: this.sourceUrl,
collInfo: this.collInfo, collInfo: this.collInfo,
@@ -295,7 +300,7 @@ class Coll extends LitElement
this.collInfo = {...this.collInfo, ...event.detail}; this.collInfo = {...this.collInfo, ...event.detail};
} }
onHashChange(event) { onHashChange() {
const hash = window.location.hash; const hash = window.location.hash;
if (hash && hash !== this._locationHash) { if (hash && hash !== this._locationHash) {
this.tabData = Object.fromEntries(new URLSearchParams(hash.slice(1)).entries()); this.tabData = Object.fromEntries(new URLSearchParams(hash.slice(1)).entries());
@@ -337,6 +342,11 @@ class Coll extends LitElement
} }
onCollTabNav(event) { onCollTabNav(event) {
if (event.detail.reload) {
this.onRefresh(null, true);
return;
}
if (event.target.id === this.tabData.view || event.target.id === "replay" && this.tabData.url) { if (event.target.id === this.tabData.view || event.target.id === "replay" && this.tabData.url) {
this.updateTabData(event.detail.data, event.detail.replaceLoc, false); this.updateTabData(event.detail.data, event.detail.replaceLoc, false);
} else if (this.showSidebar && this.tabData.url) { } else if (this.showSidebar && this.tabData.url) {
@@ -344,7 +354,7 @@ class Coll extends LitElement
} }
} }
updateTabData(data, replaceLoc = false, merge = false) { updateTabData(data, replaceLoc = false/*, merge = false*/) {
this.tabData = {...this.tabData, ...data}; this.tabData = {...this.tabData, ...data};
if (this.tabData.url) { if (this.tabData.url) {
this.url = this.tabData.url || ""; this.url = this.tabData.url || "";
@@ -622,7 +632,7 @@ class Coll extends LitElement
return html` return html`
${this.renderLocationBar()} ${this.renderLocationBar()}
<div id="tabContents"> <div id="tabContents">
<div id="contents" class="is-light ${isSidebar ? 'sidebar' : (isReplay ? 'is-hidden' : 'full-pages')}" <div id="contents" class="is-light ${isSidebar ? "sidebar" : (isReplay ? "is-hidden" : "full-pages")}"
role="${isSidebar ? "complementary" : ""}" aria-label="${isSidebar ? "Browse Contents" : ""}"> role="${isSidebar ? "complementary" : ""}" aria-label="${isSidebar ? "Browse Contents" : ""}">
${this.renderTabHeader(isSidebar)} ${this.renderTabHeader(isSidebar)}
${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``} ${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``}
@@ -641,7 +651,7 @@ class Coll extends LitElement
@replay-favicons="${this.onFavIcons}" @replay-favicons="${this.onFavIcons}"
> >
</wr-coll-replay> </wr-coll-replay>
` : ``} ` : ""}
</div> </div>
`; `;
} else { } else {
@@ -655,7 +665,7 @@ class Coll extends LitElement
// } // }
return html` return html`
<nav class="main tabs is-centered ${isSidebar ? 'sidebar' : ''}" aria-label="tabs"> <nav class="main tabs is-centered ${isSidebar ? "sidebar" : ""}" aria-label="tabs">
<ul> <ul>
${isSidebar ? html` ${isSidebar ? html`
<li class="sidebar-nav left"> <li class="sidebar-nav left">
@@ -664,34 +674,34 @@ class Coll extends LitElement
<span class="nav-hover" aria-hidden="true">Hide</span> <span class="nav-hover" aria-hidden="true">Hide</span>
<span class="is-sr-only">Hide Sidebar</span> <span class="is-sr-only">Hide Sidebar</span>
</a> </a>
</li>` : ``} </li>` : ""}
${this.hasStory ? html` ${this.hasStory ? html`
<li class="${this.tabData.view === 'story' ? 'is-active' : ''}"> <li class="${this.tabData.view === "story" ? "is-active" : ""}">
<a @click="${this.onTabClick}" href="#story" class="is-size-6" aria-label="Story" aria-current="${this.tabData.view === 'story' ? 'location' : ''}"> <a @click="${this.onTabClick}" href="#story" class="is-size-6" aria-label="Story" aria-current="${this.tabData.view === "story" ? "location" : ""}">
<span class="icon"><fa-icon .svg="${fasBook}" aria-hidden="true" title="Story"></fa-icon></span> <span class="icon"><fa-icon .svg="${fasBook}" aria-hidden="true" title="Story"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Story">Story</span> <span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="Story">Story</span>
</a> </a>
</li>` : ``} </li>` : ""}
<li class="${this.tabData.view === 'pages' ? 'is-active' : ''}"> <li class="${this.tabData.view === "pages" ? "is-active" : ""}">
<a @click="${this.onTabClick}" href="#pages" class="is-size-6" aria-label="Pages" aria-current="${this.tabData.view === 'pages' ? 'location' : ''}"> <a @click="${this.onTabClick}" href="#pages" class="is-size-6" aria-label="Pages" aria-current="${this.tabData.view === "pages" ? "location" : ""}">
<span class="icon"><fa-icon .svg="${farPages}" aria-hidden="true" title="Pages"></fa-icon></span> <span class="icon"><fa-icon .svg="${farPages}" aria-hidden="true" title="Pages"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Pages">Pages</span> <span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="Pages">Pages</span>
</a> </a>
</li> </li>
<li class="${this.tabData.view === 'resources' ? 'is-active' : ''}"> <li class="${this.tabData.view === "resources" ? "is-active" : ""}">
<a @click="${this.onTabClick}" href="#resources" class="is-size-6" aria-label="URLs" aria-current="${this.tabData.view === 'resources' ? 'location' : ''}"> <a @click="${this.onTabClick}" href="#resources" class="is-size-6" aria-label="URLs" aria-current="${this.tabData.view === "resources" ? "location" : ""}">
<span class="icon"><fa-icon .svg="${farResources}" aria-hidden="true" title="URLs"></fa-icon></span> <span class="icon"><fa-icon .svg="${farResources}" aria-hidden="true" title="URLs"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="URLs">URLs</span> <span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="URLs">URLs</span>
</a> </a>
</li> </li>
<li class="${this.tabData.view === 'info' ? 'is-active' : ''}"> <li class="${this.tabData.view === "info" ? "is-active" : ""}">
<a @click="${this.onTabClick}" href="#info" class="is-size-6" aria-label="Archive Info" aria-current="${this.tabData.view === 'info' ? 'location' : ''}"> <a @click="${this.onTabClick}" href="#info" class="is-size-6" aria-label="Archive Info" aria-current="${this.tabData.view === "info" ? "location" : ""}">
<span class="icon"><fa-icon .svg="${fasInfoIcon}" aria-hidden="true" title="Archive Info"></fa-icon></span> <span class="icon"><fa-icon .svg="${fasInfoIcon}" aria-hidden="true" title="Archive Info"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Archive Info">Info</span> <span class="tab-label ${isSidebar ? "is-hidden" : ""}" title="Archive Info">Info</span>
</a> </a>
</li> </li>
@@ -702,7 +712,7 @@ class Coll extends LitElement
<span class="is-sr-only">Expand Sidebar to Full View</span> <span class="is-sr-only">Expand Sidebar to Full View</span>
<fa-icon title="Expand" .svg="${fasAngleRight}" aria-hidden="true"></fa-icon> <fa-icon title="Expand" .svg="${fasAngleRight}" aria-hidden="true"></fa-icon>
</a> </a>
</li>` : ``} </li>` : ""}
</ul> </ul>
</nav>`; </nav>`;
} }
@@ -740,15 +750,15 @@ class Coll extends LitElement
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon>
</span> </span>
</a> </a>
<a href="#" role="button" class="button narrow is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" id="refresh" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}" <a href="#" role="button" class="button narrow is-borderless ${this.isLoading ? "is-loading" : "is-hidden-mobile"}" id="refresh" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}"
title="Reload" aria-label="Reload"> title="Reload" aria-label="Reload">
<span class="icon is-small"> <span class="icon is-small">
${!this.isLoading ? html` ${!this.isLoading ? html`
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRefresh}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRefresh}"></fa-icon>
` : ``} ` : ""}
</span> </span>
</a> </a>
<a href="#" role="button" class="button narrow is-borderless is-hidden-mobile ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}" <a href="#" role="button" class="button narrow is-borderless is-hidden-mobile ${!isReplay ? "grey-disabled" : ""}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"
?disabled="${!isReplay}" title="Browse Contents" aria-label="Browse Contents" aria-controls="contents"> ?disabled="${!isReplay}" title="Browse Contents" aria-label="Browse Contents" aria-controls="contents">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
@@ -756,7 +766,7 @@ class Coll extends LitElement
</a> </a>
${this.renderExtraToolbar(false)} ${this.renderExtraToolbar(false)}
<form @submit="${this.onSubmit}"> <form @submit="${this.onSubmit}">
<div class="control is-expanded ${showFavIcon ? 'has-icons-left' : ''}"> <div class="control is-expanded ${showFavIcon ? "has-icons-left" : ""}">
<input id="url" class="input" type="text" @keydown="${this.onKeyDown}" @blur="${this.onLostFocus}" .value="${this.url}" placeholder="Enter text to search or a URL to replay"/> <input id="url" class="input" type="text" @keydown="${this.onKeyDown}" @blur="${this.onLostFocus}" .value="${this.url}" placeholder="Enter text to search or a URL to replay"/>
${isReplay ? html`<p id="datetime" class="control is-hidden-mobile">${dateStr}</p>` : html``} ${isReplay ? html`<p id="datetime" class="control is-hidden-mobile">${dateStr}</p>` : html``}
${showFavIcon ? html` ${showFavIcon ? html`
@@ -766,7 +776,7 @@ class Coll extends LitElement
</div> </div>
</form> </form>
<div class="dropdown is-right ${this.menuActive ? 'is-active' : ''}" @click="${(e) => this.menuActive = false}"> <div class="dropdown is-right ${this.menuActive ? "is-active" : ""}" @click="${() => this.menuActive = false}">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button class="button is-borderless" aria-haspopup="true" aria-controls="menu-dropdown" aria-expanded="${this.menuActive}" @click="${this.onMenu}" <button class="button is-borderless" aria-haspopup="true" aria-controls="menu-dropdown" aria-expanded="${this.menuActive}" @click="${this.onMenu}"
aria-label="more replay controls"> aria-label="more replay controls">
@@ -801,7 +811,7 @@ class Coll extends LitElement
</span> </span>
<span>Reload</span> <span>Reload</span>
</a> </a>
<a href="#" role="button" class="dropdown-item is-hidden-tablet ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"> <a href="#" role="button" class="dropdown-item is-hidden-tablet ${!isReplay ? "grey-disabled" : ""}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small"> <span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon> <fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
</span> </span>
@@ -820,12 +830,12 @@ class Coll extends LitElement
<hr class="dropdown-divider is-hidden-desktop"> <hr class="dropdown-divider is-hidden-desktop">
<div class="dropdown-item info is-hidden-desktop"> <div class="dropdown-item info is-hidden-desktop">
<span class="menu-head">Capture Date</span>${dateStr} <span class="menu-head">Capture Date</span>${dateStr}
</div>` : ``} </div>` : ""}
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a href="#" role="button" class="dropdown-item" @click="${this.onAbout}"> <a href="#" role="button" class="dropdown-item" @click="${this.onAbout}">
<fa-icon class="menu-logo" size="1.0rem" aria-hidden="true" .svg=${this.appLogo}></fa-icon> <fa-icon class="menu-logo" size="1.0rem" aria-hidden="true" .svg=${this.appLogo}></fa-icon>
<span>&nbsp;About ${this.appName}</span> <span>&nbsp;About ${this.appName}</span>
<span class="menu-version">(${__VERSION__})</span> <span class="menu-version">(${VERSION})</span>
</a> </a>
</div> </div>
</div> </div>
@@ -861,15 +871,15 @@ class Coll extends LitElement
</div>`; </div>`;
} }
renderExtraToolbar(isDropdown = false) { renderExtraToolbar(/*isDropdown = false*/) {
return ''; return "";
} }
renderCollTabs(isSidebar) { renderCollTabs(isSidebar) {
const isStory = this.hasStory && this.tabData.view === 'story'; const isStory = this.hasStory && this.tabData.view === "story";
const isPages = this.tabData.view === 'pages'; const isPages = this.tabData.view === "pages";
const isResources = this.tabData.view === 'resources'; const isResources = this.tabData.view === "resources";
const isInfo = this.tabData.view === 'info'; const isInfo = this.tabData.view === "info";
return html` return html`
@@ -881,10 +891,10 @@ class Coll extends LitElement
currList="${this.tabData.currList || 0}" currList="${this.tabData.currList || 0}"
@coll-tab-nav="${this.onCollTabNav}" id="story" @coll-tab-nav="${this.onCollTabNav}" id="story"
.isSidebar="${isSidebar}" .isSidebar="${isSidebar}"
class="${isStory ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}" class="${isStory ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? '' : 'main'}" role="${isSidebar ? "" : "main"}"
> >
</wr-coll-story>` : ''} </wr-coll-story>` : ""}
${isResources ? html` ${isResources ? html`
<wr-coll-resources .collInfo="${this.collInfo}" <wr-coll-resources .collInfo="${this.collInfo}"
@@ -894,10 +904,10 @@ class Coll extends LitElement
.currMime="${this.tabData.currMime || ""}" .currMime="${this.tabData.currMime || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="resources" @coll-tab-nav="${this.onCollTabNav}" id="resources"
.isSidebar="${isSidebar}" .isSidebar="${isSidebar}"
class="is-paddingless ${isResources ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}" class="is-paddingless ${isResources ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? '' : 'main'}" role="${isSidebar ? "" : "main"}"
> >
</wr-coll-resources>` : ``} </wr-coll-resources>` : ""}
${isPages ? html` ${isPages ? html`
<wr-page-view <wr-page-view
@@ -911,10 +921,10 @@ class Coll extends LitElement
.ts="${this.tabData.ts || ""}" .ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onCollTabNav}" id="pages" @coll-tab-nav="${this.onCollTabNav}" id="pages"
@coll-update="${this.onCollUpdate}" @coll-update="${this.onCollUpdate}"
class="${isPages ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}" class="${isPages ? "" : "is-hidden"} ${isSidebar ? "sidebar" : ""}"
role="${isSidebar ? '' : 'main'}" role="${isSidebar ? "" : "main"}"
> >
</wr-page-view>` : ``} </wr-page-view>` : ""}
`; `;
} }
@@ -922,7 +932,7 @@ class Coll extends LitElement
// This is a workaround, since this app's routing doesn't permit normal // This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors. // following of in-page anchors.
event.preventDefault(); event.preventDefault();
this.renderRoot.querySelector("#skip-replay-target").focus() this.renderRoot.querySelector("#skip-replay-target").focus();
} }
onKeyDown(event) { onKeyDown(event) {
@@ -1015,7 +1025,7 @@ class Coll extends LitElement
const deleteURL = this.collInfo.apiPrefix + (reload ? "?reload=1" : ""); const deleteURL = this.collInfo.apiPrefix + (reload ? "?reload=1" : "");
const resp = await fetch(deleteURL, { const resp = await fetch(deleteURL, {
method: 'DELETE', method: "DELETE",
}); });
if (resp.status !== 200) { if (resp.status !== 200) {
@@ -1027,7 +1037,7 @@ class Coll extends LitElement
return; return;
} }
window.location.hash = ""; //window.location.hash = "";
window.location.reload(); window.location.reload();
} }
@@ -1099,7 +1109,7 @@ class Coll extends LitElement
return q.toString(); return q.toString();
} }
onRefresh(event) { onRefresh(event, replayOnly = false) {
if (event) { if (event) {
event.preventDefault(); event.preventDefault();
} }
@@ -1111,7 +1121,7 @@ class Coll extends LitElement
if (replay) { if (replay) {
replay.refresh(); replay.refresh();
} }
} else { } else if (!replayOnly) {
window.location.reload(); window.location.reload();
} }
} }
+4 -2
View File
@@ -1,5 +1,7 @@
import { ElectronReplayApp } from './electron-replay-app'; /*eslint-env node */
import path from 'path';
import { ElectronReplayApp } from "./electron-replay-app";
import path from "path";
// ============================================================================ // ============================================================================
+5 -5
View File
@@ -1,10 +1,10 @@
"use strict"; /*eslint-env node */
import { CollectionLoader } from '@webrecorder/wabac/src/loaders'; import { CollectionLoader } from "@webrecorder/wabac/src/loaders";
const { ipcRenderer, contextBridge } = require('electron'); const { ipcRenderer, contextBridge } = require("electron");
contextBridge.exposeInMainWorld('electron', {'IS_APP': true}); contextBridge.exposeInMainWorld("electron", {"IS_APP": true});
const dbs = {}; const dbs = {};
@@ -43,7 +43,7 @@ async function getResponse(event, request, coll, ts, channel) {
ipcRenderer.send(channel, result.status, headers, buffer); ipcRenderer.send(channel, result.status, headers, buffer);
} }
ipcRenderer.on('getresponse', getResponse); ipcRenderer.on("getresponse", getResponse);
export { getDB, getColl, loader }; export { getDB, getColl, loader };
+40 -37
View File
@@ -1,29 +1,31 @@
import fetch from 'node-fetch'; /*eslint-env node */
import { Headers } from 'node-fetch';
import {app, session, BrowserWindow, ipcMain, screen, shell } from 'electron'; import fetch from "node-fetch";
import { Headers } from "node-fetch";
import path from 'path'; import {app, session, BrowserWindow, ipcMain, screen, shell } from "electron";
import fs from 'fs';
import { ArchiveResponse, Rewriter } from '@webrecorder/wabac/src/rewrite'; import path from "path";
import { IPFSClient } from '@webrecorder/wabac/src/ipfs'; import fs from "fs";
import { PassThrough, Readable } from 'stream'; import { ArchiveResponse, Rewriter } from "@webrecorder/wabac/src/rewrite";
import { IPFSClient } from "@webrecorder/wabac/src/ipfs";
import { PassThrough, Readable } from "stream";
import { autoUpdater } from "electron-updater"; import { autoUpdater } from "electron-updater";
import log from "electron-log"; import log from "electron-log";
import mime from 'mime-types'; import mime from "mime-types";
global.Headers = Headers; global.Headers = Headers;
global.fetch = fetch; global.fetch = fetch;
const STATIC_PREFIX = "http://localhost:5471/"; const STATIC_PREFIX = "http://localhost:5471/";
const REPLAY_PREFIX = STATIC_PREFIX + "wabac/"; const REPLAY_PREFIX = STATIC_PREFIX + "w/";
const URL_RX = /([^\/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/; const URL_RX = /([^/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
let IPFS = null; let IPFS = null;
@@ -76,11 +78,11 @@ class ElectronReplayApp
get mainWindowWebPreferences() { get mainWindowWebPreferences() {
return { return {
plugins: true, plugins: true,
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, "preload.js"),
nativeWindowOpen: true, nativeWindowOpen: true,
contextIsolation: true, contextIsolation: true,
enableRemoteModule: false enableRemoteModule: false
} };
} }
get mainWindowUrl() { get mainWindowUrl() {
@@ -95,7 +97,7 @@ class ElectronReplayApp
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, workingDirectory) => { app.on("second-instance", (event, commandLine/*, workingDir*/) => {
// Just create a new window in case of second instance request // Just create a new window in case of second instance request
this.createMainWindow(commandLine); this.createMainWindow(commandLine);
}); });
@@ -103,43 +105,43 @@ class ElectronReplayApp
if (includePlugins) { if (includePlugins) {
switch (process.platform) { switch (process.platform) {
case 'win32': case "win32":
this.pluginPath = path.join(this.projPath, "plugins-win", `pepflashplayer-x86${process.arch === 'x64' ? '_64' : ''}.dll`); this.pluginPath = path.join(this.projPath, "plugins-win", `pepflashplayer-x86${process.arch === "x64" ? "_64" : ""}.dll`);
break; break;
case 'darwin': case "darwin":
this.pluginPath = path.join(this.projPath, "plugins-mac", "PepperFlashPlayer.plugin"); this.pluginPath = path.join(this.projPath, "plugins-mac", "PepperFlashPlayer.plugin");
break; break;
case 'linux': case "linux":
this.pluginPath = path.join(this.projPath, "plugins-mac", "libpepflashplayer.so"); this.pluginPath = path.join(this.projPath, "plugins-mac", "libpepflashplayer.so");
break; break;
default: default:
console.log('platform unsupported: ' + process.platform); console.log("platform unsupported: " + process.platform);
break; break;
} }
app.commandLine.appendSwitch('ppapi-flash-path', this.pluginPath); app.commandLine.appendSwitch("ppapi-flash-path", this.pluginPath);
} }
console.log('app path', this.appPath); console.log("app path", this.appPath);
console.log('dir name', __dirname); console.log("dir name", __dirname);
console.log('proj path', this.projPath); console.log("proj path", this.projPath);
if (includePlugins) { if (includePlugins) {
console.log('plugin path', this.pluginPath); console.log("plugin path", this.pluginPath);
} }
console.log('app data', app.getPath('appData')); console.log("app data", app.getPath("appData"));
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));
} }
app.on('will-finish-launching', () => { app.on("will-finish-launching", () => {
app.on('open-file', (event, filePath) => { app.on("open-file", (event, filePath) => {
this.openNextFile = filePath; this.openNextFile = filePath;
if (this.mainWindow) { if (this.mainWindow) {
this.createMainWindow(process.argv); this.createMainWindow(process.argv);
@@ -147,7 +149,7 @@ class ElectronReplayApp
}); });
}); });
app.on('activate', function () { app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the // On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) { if (BrowserWindow.getAllWindows().length === 0) {
@@ -158,7 +160,7 @@ class ElectronReplayApp
app.whenReady().then(() => this.onAppReady()); app.whenReady().then(() => this.onAppReady());
// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', function () { app.on("window-all-closed", function () {
// On macOS it is common for applications and their menu bar // On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q // to stay active until the user quits explicitly with Cmd + Q
//if (process.platform !== 'darwin') //if (process.platform !== 'darwin')
@@ -177,8 +179,8 @@ class ElectronReplayApp
this.screenSize = screen.getPrimaryDisplay().workAreaSize; this.screenSize = screen.getPrimaryDisplay().workAreaSize;
app.on('web-contents-created', (event, contents) => { app.on("web-contents-created", (event, contents) => {
contents.on('new-window', async (event, navigationUrl) => { contents.on("new-window", async (event, navigationUrl) => {
// load docs in native browser for now // load docs in native browser for now
if (navigationUrl === STATIC_PREFIX + "docs") { if (navigationUrl === STATIC_PREFIX + "docs") {
@@ -251,6 +253,7 @@ class ElectronReplayApp
return this.notFound(request.url, callback); return this.notFound(request.url, callback);
} }
// eslint-disable-next-line no-undef
if (request.url.startsWith(__APP_FILE_SERVE_PREFIX__)) { if (request.url.startsWith(__APP_FILE_SERVE_PREFIX__)) {
const parsedUrl = new URL(request.url); const parsedUrl = new URL(request.url);
@@ -330,7 +333,7 @@ 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(`Sorry, the url <b>${url}</b> could not be found in this archive.`);
callback({statusCode: 404, headers: {"Content-Type": 'text/html; charset="utf-8"'}, data}); callback({statusCode: 404, headers: {"Content-Type": "text/html; charset=\"utf-8\""}, data});
} }
async resolveArchiveResponse(request, callback) { async resolveArchiveResponse(request, callback) {
@@ -340,7 +343,7 @@ class ElectronReplayApp
const url = request.url; const url = request.url;
if (status === 404 && !payload) { if (status === 404 && !payload) {
return notFound(url, callback); return this.notFound(url, callback);
} else { } else {
console.log("got response for: " + url); console.log("got response for: " + url);
} }
@@ -426,7 +429,7 @@ class ElectronReplayApp
isMaximized: true, isMaximized: true,
show: false, show: false,
webPreferences: this.mainWindowWebPreferences, webPreferences: this.mainWindowWebPreferences,
}).once('ready-to-show', () => { }).once("ready-to-show", () => {
theWindow.show(); theWindow.show();
theWindow.maximize(); theWindow.maximize();
}); });
@@ -440,7 +443,7 @@ 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;
+16 -8
View File
@@ -1,8 +1,9 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined.js";
import { registerSW } from './pageutils'; import { registerSW } from "./pageutils";
import { wrapCss, rwpLogo } from './misc'; import { wrapCss, rwpLogo } from "./misc";
var scriptSrc = document.currentScript && document.currentScript.src; var scriptSrc = document.currentScript && document.currentScript.src;
@@ -14,6 +15,7 @@ class Embed extends LitElement
constructor() { constructor() {
super(); super();
this.replaybase = "./replay/"; this.replaybase = "./replay/";
// eslint-disable-next-line no-undef
this.swName = __SW_NAME__; this.swName = __SW_NAME__;
this.view = "replay"; this.view = "replay";
this.ts = ""; this.ts = "";
@@ -26,6 +28,7 @@ class Embed extends LitElement
this.swInited = false; this.swInited = false;
this.embed = null; this.embed = null;
this.reloadCount = 0; this.reloadCount = 0;
this.noSandbox = false;
} }
static get properties() { static get properties() {
@@ -53,7 +56,8 @@ class Embed extends LitElement
deepLink: { type: Boolean }, deepLink: { type: Boolean },
noSW: { type: Boolean }, noSW: { type: Boolean },
} noSandbox: { type: Boolean }
};
} }
async doRegister() { async doRegister() {
@@ -91,7 +95,7 @@ class Embed extends LitElement
if (this.deepLink) { if (this.deepLink) {
this.updateFromHash(); this.updateFromHash();
window.addEventListener("hashchange", (event) => this.updateFromHash()); window.addEventListener("hashchange", () => this.updateFromHash());
} }
} }
@@ -170,9 +174,13 @@ class Embed extends LitElement
render() { render() {
return html` return html`
${this.paramString && this.hashString && this.swInited ? html` ${this.paramString && this.hashString && this.swInited ? html`
<iframe sandbox="allow-downloads allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-scripts allow-same-origin allow-forms" <iframe sandbox="${ifDefined(!this.noSandbox ?
"allow-downloads allow-modals allow-orientation-lock allow-pointer-lock\
allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts\
allow-same-origin allow-forms" : undefined)}"
@load="${this.onLoad}" src="${this.replaybase}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen" @load="${this.onLoad}" src="${this.replaybase}?${this.paramString}#${this.hashString}" allow="autoplay *; fullscreen"
title="Replay of ${this.title ? `${this.title}:` :``} ${this.url}"></iframe> title="Replay of ${this.title ? `${this.title}:` :""} ${this.url}"></iframe>
` : html``} ` : html``}
@@ -187,7 +195,7 @@ Please try a different browser.\n
(Service Workers are disabled in Firefox in Private Mode. If Using Private Mode in Firefox, try regular mode). (Service Workers are disabled in Firefox in Private Mode. If Using Private Mode in Firefox, try regular mode).
</div> </div>
</section> </section>
`: ``}`; `: ""}`;
} }
onLoad(event) { onLoad(event) {
+15 -13
View File
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss } from './misc'; import { wrapCss } from "./misc";
import fabGoogleDrive from '@fortawesome/fontawesome-free/svgs/brands/google-drive.svg'; import fabGoogleDrive from "@fortawesome/fontawesome-free/svgs/brands/google-drive.svg";
// =========================================================================== // ===========================================================================
@@ -21,7 +21,7 @@ class GDrive extends LitElement
sourceUrl: { type: String }, sourceUrl: { type: String },
error: { type: Boolean }, error: { type: Boolean },
reauth: { type: Boolean } reauth: { type: Boolean }
} };
} }
updated(changedProperties) { updated(changedProperties) {
@@ -41,6 +41,7 @@ class GDrive extends LitElement
try { try {
const sourceUrl = this.sourceUrl; const sourceUrl = this.sourceUrl;
const fileId = sourceUrl.slice("googledrive://".length); const fileId = sourceUrl.slice("googledrive://".length);
// eslint-disable-next-line no-undef
const publicCheckUrl = `${__HELPER_PROXY__}/g/${fileId}`; const publicCheckUrl = `${__HELPER_PROXY__}/g/${fileId}`;
let resp = null; let resp = null;
@@ -87,7 +88,7 @@ class GDrive extends LitElement
onLoad() { onLoad() {
this.scriptLoaded = true; this.scriptLoaded = true;
this.gauth('none', (response) => { this.gauth("none", (response) => {
if (response.error) { if (response.error) {
if (this.state !== "implicitonly") { if (this.state !== "implicitonly") {
this.state = "trymanual"; this.state = "trymanual";
@@ -99,7 +100,7 @@ class GDrive extends LitElement
} }
onClickAuth() { onClickAuth() {
this.gauth('select_account', (response) => { this.gauth("select_account", (response) => {
if (!response.error) { if (!response.error) {
this.authed(response); this.authed(response);
} }
@@ -144,12 +145,12 @@ class GDrive extends LitElement
` : html` ` : html`
${this.error ? html` ${this.error ? 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>${this.reauth ? "Some resources are loaded on demand from Google Drive, which requires reauthorization." :
'Could not access this file with the current Google Drive account.'}</p> "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> <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}"> <button class="button is-warning is-rounded" @click="${this.onClickAuth}">
<span class="icon"><fa-icon .svg="${fabGoogleDrive}"></fa-icon></span> <span class="icon"><fa-icon .svg="${fabGoogleDrive}"></fa-icon></span>
<span>Authorize Google Drive</span> <span>Authorize Google Drive</span>
@@ -161,15 +162,16 @@ class GDrive extends LitElement
if (this.state === "trypublic" || this.scriptLoaded) { if (this.state === "trypublic" || this.scriptLoaded) {
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) {
gapi.load('auth2', () => { self.gapi.load("auth2", () => {
gapi.auth2.authorize({ self.gapi.auth2.authorize({
// 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",
+15 -12
View File
@@ -1,9 +1,9 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss, IS_APP } from './misc'; import { wrapCss, IS_APP } from "./misc";
import prettyBytes from 'pretty-bytes'; import prettyBytes from "pretty-bytes";
import { parseURLSchemeHostPath } from './pageutils'; import { parseURLSchemeHostPath } from "./pageutils";
// =========================================================================== // ===========================================================================
@@ -46,10 +46,10 @@ class Loader extends LitElement
embed: { type: String }, embed: { type: String },
tryFileHandle: { type: Boolean }, tryFileHandle: { type: Boolean },
errorAllowRetry: { type: Boolean } errorAllowRetry: { type: Boolean }
} };
} }
firstUpdated(props) { firstUpdated() {
this.initMessages(); this.initMessages();
//this.doLoad(); //this.doLoad();
} }
@@ -140,7 +140,7 @@ Please try loading this page from an HTTPS URL`;
this.state = "errored"; this.state = "errored";
this.error = `\ this.error = `\
File URLs can not be entered directly or shared. File URLs can not be entered directly or shared.
You can select a file to upload from the main page by clicking the \'Choose File...\' button.`; You can select a file to upload from the main page by clicking the 'Choose File...' button.`;
this.errorAllowRetry = false; this.errorAllowRetry = false;
return; return;
} }
@@ -150,6 +150,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
case "ipfs": case "ipfs":
if (IS_APP) { if (IS_APP) {
// eslint-disable-next-line no-undef
const url = new URL(__APP_FILE_SERVE_PREFIX__); const url = new URL(__APP_FILE_SERVE_PREFIX__);
const hash = sourceUrl.split("#", 1)[0]; const hash = sourceUrl.split("#", 1)[0];
url.searchParams.set("ipfs", hash.slice("ipfs://".length)); url.searchParams.set("ipfs", hash.slice("ipfs://".length));
@@ -165,6 +166,8 @@ You can select a file to upload from the main page by clicking the \'Choose File
source = {sourceUrl}; source = {sourceUrl};
} }
source.newFullImport = (this.loadInfo && this.loadInfo.newFullImport);
this.state = "started"; this.state = "started";
const msg = {"msg_type": "addColl", "name": this.coll, skipExisting: true, file: source}; const msg = {"msg_type": "addColl", "name": this.coll, skipExisting: true, file: source};
@@ -175,7 +178,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
if (!navigator.serviceWorker.controller) { if (!navigator.serviceWorker.controller) {
await new Promise((resolve) => { await new Promise((resolve) => {
navigator.serviceWorker.addEventListener("controllerchange", () => resolve); navigator.serviceWorker.addEventListener("controllerchange", () => resolve());
}); });
} }
@@ -269,7 +272,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
${!this.embed ? html` ${!this.embed ? html`
<div class="level"> <div class="level">
<p class="level-item">Loading&nbsp;<b>${this.sourceUrl}</b>...</p> <p class="level-item">Loading&nbsp;<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()}
@@ -282,7 +285,7 @@ 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`
@@ -294,7 +297,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
<p class="loaded-prog">Loaded <b>${prettyBytes(this.currentSize)}</b> of <b>${prettyBytes(this.totalSize)}</b></p>` : html``} <p class="loaded-prog">Loaded <b>${prettyBytes(this.currentSize)}</b> of <b>${prettyBytes(this.totalSize)}</b></p>` : html``}
${!this.embed ? html` ${!this.embed ? html`
<button @click="${this.onCancel}" class="button is-danger">Cancel</button>` : ``} <button @click="${this.onCancel}" class="button is-danger">Cancel</button>` : ""}
</div>`; </div>`;
case "errored": case "errored":
@@ -303,7 +306,7 @@ You can select a file to upload from the main page by clicking the \'Choose File
<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 ? html`
<a class="button is-warning" @click=${(e) => window.parent.location.reload()}>Try Again</a>` : ``} <a class="button is-warning" @click=${() => window.parent.location.reload()}>Try Again</a>` : ""}
${this.embed ? html`` : html` ${this.embed ? html`` : html`
<a href="/" class="button is-warning">Back</a>`} <a href="/" class="button is-warning">Back</a>`}
</div>`; </div>`;
+15 -10
View File
@@ -1,12 +1,15 @@
"use strict"; "use strict";
import { LitElement, html, css, unsafeCSS } from 'lit-element'; import { LitElement, html, css, unsafeCSS } from "lit-element";
import { unsafeSVG } from 'lit-html/directives/unsafe-svg'; import { unsafeSVG } from "lit-html/directives/unsafe-svg";
import { styleMap } from 'lit-html/directives/style-map'; import { styleMap } from "lit-html/directives/style-map";
import allCssRaw from '../assets/main.scss'; import allCssRaw from "../assets/main.scss";
import rwpLogo from '../assets/logo.svg'; import rwpLogo from "../assets/logo.svg";
const apiPrefix = "./w/api";
const replayPrefix = "./w";
// =========================================================================== // ===========================================================================
@@ -15,8 +18,10 @@ function wrapCss(custom) {
return [allCss, custom]; return [allCss, custom];
} }
const IS_APP = window.IS_APP || window.electron && window.electron.IS_APP || window.matchMedia('(display-mode: standalone)').matches; const IS_APP = window.IS_APP || window.electron && window.electron.IS_APP || window.matchMedia("(display-mode: standalone)").matches;
// eslint-disable-next-line no-undef
const VERSION = __VERSION__;
// =========================================================================== // ===========================================================================
// Buttons are expected to respond to both enter/return and spacebar. // Buttons are expected to respond to both enter/return and spacebar.
@@ -45,7 +50,7 @@ class FaIcon extends LitElement
size: { type: String }, size: { type: String },
width: { type: String }, width: { type: String },
height: { type: String } height: { type: String }
} };
} }
static get styles() { static get styles() {
@@ -137,7 +142,7 @@ class WrModal extends LitElement
return { return {
title: { type: String }, title: { type: String },
bgClass: { type: String } bgClass: { type: String }
} };
} }
static get styles() { static get styles() {
@@ -178,5 +183,5 @@ customElements.define("wr-anim-logo", AnimLogo);
customElements.define("wr-modal", WrModal); customElements.define("wr-modal", WrModal);
export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo, WrModal, export { wrapCss, IS_APP, VERSION, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo, WrModal,
LitElement, html, css, unsafeCSS, unsafeSVG }; LitElement, html, css, unsafeCSS, unsafeSVG, apiPrefix, replayPrefix };
+27 -23
View File
@@ -1,12 +1,12 @@
import prettyBytes from 'pretty-bytes'; import prettyBytes from "pretty-bytes";
import { LitElement, html, css, unsafeCSS } from 'lit-element'; import { LitElement, html, css, unsafeCSS } from "lit-element";
import "keyword-mark-element/lib/keyword-mark.js"; import "keyword-mark-element/lib/keyword-mark.js";
import { getReplayLink } from './pageutils'; import { getReplayLink } from "./pageutils";
import { wrapCss } from './misc'; import { wrapCss } from "./misc";
// =========================================================================== // ===========================================================================
@@ -39,7 +39,7 @@ class PageEntry extends LitElement
index: { type: Number }, index: { type: Number },
isCurrent: { type: Boolean }, isCurrent: { type: Boolean },
isSidebar: { type: Boolean }, isSidebar: { type: Boolean },
} };
} }
static get styles() { static get styles() {
@@ -95,7 +95,7 @@ class PageEntry extends LitElement
align-self: center; align-self: center;
} }
.delete { .delete-button {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; right: 8px;
@@ -190,12 +190,12 @@ class PageEntry extends LitElement
<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>
@@ -205,13 +205,13 @@ class PageEntry extends LitElement
<figure class="media-left"> <figure class="media-left">
<p class=""> <p class="">
${this.iconValid ? html` ${this.iconValid ? html`
<img class="favicon" @error="${(e) => this.iconValid = false}" src="${this.replayPrefix}/${this.page.timestamp}id_/${p.favIconUrl}"/>` : html` <img class="favicon" @error="${() => this.iconValid = false}" src="${this.replayPrefix}/${this.page.timestamp}id_/${p.favIconUrl}"/>` : html`
<span class="favicon"></span>`} <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 @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>
@@ -226,16 +226,16 @@ class PageEntry extends LitElement
${hasSize ? html` ${hasSize ? html`
<div class="media-right" style="margin-right: 2em"> <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 ? html`
${!this.deleting ? html` ${!this.deleting ? html`
<button @click="${this.onSendDeletePage}" class="delete"></button>` : html` <button @click="${this.onSendDeletePage}" class="delete delete-button"></button>` : html`
<button class="button delete is-loading is-static"></button> <button class="button is-loading delete-button is-static"></button>
`}` : ''} `}` : ""}
`; `;
} }
@@ -268,7 +268,7 @@ class PageEntry extends LitElement
if (!this.query || !this.page.text) { if (!this.query || !this.page.text) {
this.textSnippet = null; this.textSnippet = null;
this.requestUpdate('textSnippet', oldVal); this.requestUpdate("textSnippet", oldVal);
return; return;
} }
@@ -285,7 +285,7 @@ class PageEntry extends LitElement
if (inx < 0) { if (inx < 0) {
this.textSnippet = null; this.textSnippet = null;
this.requestUpdate('textSnippet', oldVal); this.requestUpdate("textSnippet", oldVal);
return; return;
} }
@@ -305,25 +305,29 @@ class PageEntry extends LitElement
this.textSnippet = "..." + textContent.slice(inx, lastInx) + "..."; this.textSnippet = "..." + textContent.slice(inx, lastInx) + "...";
} }
this.requestUpdate('textSnippet', oldVal); this.requestUpdate("textSnippet", oldVal);
} }
onReplay(event) { onReplay(event, reload = false) {
event.preventDefault(); event.preventDefault();
const data = { const data = {
url: this.page.url, url: this.page.url,
ts: this.page.timestamp, ts: this.page.timestamp,
}; };
this.sendChangeEvent(data); this.sendChangeEvent(data, reload);
return false; return false;
} }
sendChangeEvent(data) { onReload(event) {
this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data}})); return this.onReplay(event, true);
} }
onSendDeletePage(event) { sendChangeEvent(data, reload) {
this.dispatchEvent(new CustomEvent("coll-tab-nav", {bubbles: true, composed: true, detail: {data, reload}}));
}
onSendDeletePage() {
const page = this.page; const page = this.page;
this.dispatchEvent(new CustomEvent("delete-page", {detail: {page}})); this.dispatchEvent(new CustomEvent("delete-page", {detail: {page}}));
} }
+40 -35
View File
@@ -1,18 +1,18 @@
"use strict"; "use strict";
import { LitElement, html, css, unsafeCSS } from 'lit-element'; import { LitElement, html, css, unsafeCSS } from "lit-element";
import { wrapCss, clickOnSpacebarPress } from './misc'; import { wrapCss, clickOnSpacebarPress } from "./misc";
import ndjson from 'fetch-ndjson'; import ndjson from "fetch-ndjson";
import FlexSearch from 'flexsearch'; import FlexSearch from "flexsearch";
import prettyBytes from 'pretty-bytes'; import prettyBytes from "pretty-bytes";
import { getTS, getPageDateTS } from './pageutils'; import { getTS, getPageDateTS } from "./pageutils";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg'; import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import fasAngleDown from '@fortawesome/fontawesome-free/svgs/solid/angle-down.svg'; import fasAngleDown from "@fortawesome/fontawesome-free/svgs/solid/angle-down.svg";
import fasEdit from '@fortawesome/fontawesome-free/svgs/solid/edit.svg'; import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
// =========================================================================== // ===========================================================================
@@ -99,7 +99,7 @@ class Pages extends LitElement
toDeletePages: { type: Object }, toDeletePages: { type: Object },
toDeletePage: { type: Object } toDeletePage: { type: Object }
} };
} }
_timedUpdate() { _timedUpdate() {
@@ -131,7 +131,7 @@ class Pages extends LitElement
this.sortKey = ""; this.sortKey = "";
this.sortDesc = false; this.sortDesc = false;
} else { } else {
this.sortKey = "date" this.sortKey = "date";
this.sortDesc = true; this.sortDesc = true;
} }
const sorter = this.renderRoot.querySelector("wr-sorter"); const sorter = this.renderRoot.querySelector("wr-sorter");
@@ -241,13 +241,13 @@ class Pages extends LitElement
let lines = []; let lines = [];
async function flush() { const flush = async () => {
let curr = lines; let curr = lines;
lines = []; lines = [];
await flex.add(curr); await flex.add(curr);
console.log("added " + count + " " + curr.length); console.log("added " + count + " " + curr.length);
console.log(flex.info()); console.log(flex.info());
} };
for await (const line of ndjson(resp.body.getReader())) { for await (const line of ndjson(resp.body.getReader())) {
if (!line.text) { if (!line.text) {
@@ -516,9 +516,9 @@ class Pages extends LitElement
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 class="control has-icons-left ${this.loading ? "is-loading" : ""}">
<input type="search" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text" <input type="search" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text"
placeholder="Search by Page URL, Title or Text"> placeholder="Search by Page URL, Title or Text">
<span class="icon is-left"><fa-icon .svg="${fasSearch}" aria-hidden="true"></fa-icon></span> <span class="icon is-left"><fa-icon .svg="${fasSearch}" aria-hidden="true"></fa-icon></span>
@@ -531,7 +531,7 @@ class Pages extends LitElement
${this.editable && this.editing ? html` ${this.editable && this.editing ? html`
<form @submit="${this.onUpdateTitle}"><input id="titleEdit" class="input" value="${this.collInfo.title}" @blur="${this.onUpdateTitle}"></form> <form @submit="${this.onUpdateTitle}"><input id="titleEdit" class="input" value="${this.collInfo.title}" @blur="${this.onUpdateTitle}"></form>
` : html` ` : html`
<div class="index-bar-title" @dblclick="${(e) => this.editing = true}">${this.collInfo.title}</div>`} <div class="index-bar-title" @dblclick="${() => this.editing = true}">${this.collInfo.title}</div>`}
${this.editable ? html`<fa-icon class="editIcon" .svg="${fasEdit}"></fa-icon>` : html``} ${this.editable ? html`<fa-icon class="editIcon" .svg="${fasEdit}"></fa-icon>` : html``}
@@ -540,25 +540,25 @@ class Pages extends LitElement
${this.editable ? html` ${this.editable ? html`
<div class="index-bar-actions"> <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 href="#list-0" data-list="0" class="${currList === 0 ? "is-active" : ""}"
@click=${this.onSelectList}><i>All Pages</i></a> @click=${this.onSelectList}><i>All Pages</i></a>
</li> </li>
${this.collInfo.lists.map(list => html` ${this.collInfo.lists.map(list => html`
<li> <li>
<a @click=${this.onSelectList} href="#list-${list.id}" <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" : ""}">${list.title}</a>
</li>`)} </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>
@@ -571,7 +571,7 @@ class Pages extends LitElement
renderDownloadMenu() { renderDownloadMenu() {
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>
@@ -585,13 +585,13 @@ class Pages extends LitElement
<a role="button" href="#" <a role="button" href="#"
@click="${(e) => this.onDownload(e, "wacz", true)}" @click="${(e) => this.onDownload(e, "wacz", true)}"
@keyup="${clickOnSpacebarPress}" @keyup="${clickOnSpacebarPress}"
class="dropdown-item ${this.selectedPages.size === 0 ? 'is-disabled' : ''}"> class="dropdown-item ${this.selectedPages.size === 0 ? "is-disabled" : ""}">
Download Selected as WACZ (Web Archive Collection) Download Selected as WACZ (Web Archive Collection)
</a> </a>
<a role="button" href="#" <a role="button" href="#"
@click="${(e) => this.onDownload(e, "warc", true)}" @click="${(e) => this.onDownload(e, "warc", true)}"
@keyup="${clickOnSpacebarPress}" @keyup="${clickOnSpacebarPress}"
class="dropdown-item ${this.selectedPages.size === 0 ? 'is-disabled' : ''}"> class="dropdown-item ${this.selectedPages.size === 0 ? "is-disabled" : ""}">
Download Selected as WARC Only Download Selected as WARC Only
</a> </a>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
@@ -623,10 +623,10 @@ class Pages extends LitElement
<div class="header columns is-hidden-mobile"> <div class="header columns is-hidden-mobile">
${this.query ? html` ${this.query ? html`
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ''}">Match</a>` : ``} <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="" class="column is-1 ${this.sortKey === "" ? (this.sortDesc ? "desc" : "asc") : ""}">Match</a>` : ""}
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="date" class="column is-2 ${this.sortKey === "date" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a> <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="date" class="column is-2 ${this.sortKey === "date" ? (this.sortDesc ? "desc" : "asc") : ""}">Date</a>
<a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="title" class="column is-6 pagetitle ${this.sortKey === "title" ? (this.sortDesc ? "desc" : "asc") : ''}">Page Title</a> <a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="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">
@@ -638,7 +638,7 @@ class Pages extends LitElement
.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>
`; `;
@@ -650,7 +650,7 @@ class Pages extends LitElement
} }
return html` return html`
<wr-modal bgClass="has-background-grey-lighter" @modal-closed="${(e) => this.toDeletePages = this.toDeletePage = null}" title="Confirm Delete"> <wr-modal bgClass="has-background-grey-lighter" @modal-closed="${() => this.toDeletePages = this.toDeletePage = null}" title="Confirm Delete">
${this.toDeletePage ? html` ${this.toDeletePage ? html`
<p>Are you sure you want to delete the page <b>${this.toDeletePage.title}</b>? <p>Are you sure you want to delete the page <b>${this.toDeletePage.title}</b>?
(Size: <b>${prettyBytes(this.toDeletePage.size)}</b>)</p>` : html` (Size: <b>${prettyBytes(this.toDeletePage.size)}</b>)</p>` : html`
@@ -659,7 +659,7 @@ class Pages extends LitElement
<p>This operation can not be undone.</p> <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">Delete</button>
<button @click="${(e) => this.toDeletePages = this.toDeletePage = null}" class="button">Cancel</button> <button @click="${() => this.toDeletePages = this.toDeletePage = null}" class="button">Cancel</button>
</wr-modal>`; </wr-modal>`;
} }
@@ -691,7 +691,7 @@ class Pages extends LitElement
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}"
@@ -704,10 +704,10 @@ class Pages extends LitElement
@delete-page="${this.onDeleteConfirm}" @delete-page="${this.onDeleteConfirm}"
replayPrefix="${this.collInfo.replayPrefix}" replayPrefix="${this.collInfo.replayPrefix}"
query="${this.query}" query="${this.query}"
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>
`; `;
} }
@@ -729,7 +729,7 @@ 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}}));
} }
@@ -828,9 +828,14 @@ 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) {
console.warn(json.error);
continue;
}
const page = pageMap[id]; const page = pageMap[id];
if (!page) { if (!page) {
@@ -866,7 +871,7 @@ class Pages extends LitElement
} }
if (this.updatingSearch) { if (this.updatingSearch) {
return "Initializing Search..." return "Initializing Search...";
} }
if (this.loading) { if (this.loading) {
+10 -9
View File
@@ -1,4 +1,4 @@
import { register } from 'register-service-worker'; import { register } from "register-service-worker";
// =========================================================================== // ===========================================================================
@@ -17,13 +17,13 @@ function registerSW(name = "sw.js", scope = "./") {
register(scope + name, { register(scope + name, {
registrationOptions: { scope }, registrationOptions: { scope },
registered (reg) { registered () {
console.log('Service worker is registered'); console.log("Service worker is registered");
resolve(); resolve();
}, },
error (error) { error (error) {
console.error('Error during service worker registration:', error); console.error("Error during service worker registration:", error);
reject(); reject();
} }
}); });
@@ -36,6 +36,7 @@ let dbworker = null;
function initDBWorker() { function initDBWorker() {
if (dbworker === null) { if (dbworker === null) {
// eslint-disable-next-line no-undef
dbworker = new Worker(__SW_NAME__); dbworker = new Worker(__SW_NAME__);
} }
return dbworker; return dbworker;
@@ -47,7 +48,7 @@ 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;
} }
@@ -70,7 +71,7 @@ function tsToDate(ts) {
ts.substring(12, 14) + "-00:00"); ts.substring(12, 14) + "-00:00");
return new Date(datestr); return new Date(datestr);
}; }
// =========================================================================== // ===========================================================================
function getPageDateTS(page) { function getPageDateTS(page) {
@@ -78,7 +79,7 @@ function getPageDateTS(page) {
try { try {
date = new Date(page.ts || page.date); date = new Date(page.ts || page.date);
} catch (e) { } catch (e) {
// leave date unchanged in case of error
} }
const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : ""; const timestamp = (date && !isNaN(date)) ? getTS(date.toISOString()) : "";
@@ -88,7 +89,7 @@ function getPageDateTS(page) {
// =========================================================================== // ===========================================================================
function getTS(iso) { function getTS(iso) {
return iso.replace(/[-:T]/g, '').slice(0, 14); return iso.replace(/[-:T]/g, "").slice(0, 14);
} }
@@ -104,7 +105,7 @@ function getReplayLink(view, url, ts) {
// =========================================================================== // ===========================================================================
async function sourceToId(url) { async function sourceToId(url) {
const digest = await digestMessage(url, 'SHA-256'); const digest = await digestMessage(url, "SHA-256");
const coll = "id-" + digest.slice(0, 12); const coll = "id-" + digest.slice(0, 12);
return {url, coll}; return {url, coll};
} }
+19 -10
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss, rwpLogo } from './misc'; import { wrapCss, rwpLogo } from "./misc";
@@ -38,7 +38,7 @@ class Replay extends LitElement
showAuth: { type: Boolean }, showAuth: { type: Boolean },
authFileHandle: { type: Object } authFileHandle: { type: Object }
} };
} }
firstUpdated() { firstUpdated() {
@@ -57,7 +57,9 @@ class Replay extends LitElement
this.refresh(); this.refresh();
return; return;
} }
} catch (e) {} } catch (e) {
console.warn(e);
}
} else { } else {
this.authFileHandle = null; this.authFileHandle = null;
} }
@@ -147,8 +149,8 @@ class Replay extends LitElement
// google drive reauth // google drive reauth
const headers = event.detail.headers; const headers = event.detail.headers;
const resp = 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 {
@@ -200,9 +202,16 @@ class Replay extends LitElement
refresh() { refresh() {
const iframe = this.renderRoot.querySelector("iframe"); const iframe = this.renderRoot.querySelector("iframe");
if (iframe) { if (!iframe) {
return;
}
const oldIframeUrl = this.iframeUrl;
// set iframe url to expected, refresh if same url
this.doSetIframeUrl();
if (oldIframeUrl === this.iframeUrl) {
this.waitForLoad(); this.waitForLoad();
iframe.contentWindow.location.reload(); iframe.contentWindow.location.href = this.iframeUrl;
} }
} }
@@ -262,7 +271,7 @@ 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`
@@ -302,7 +311,7 @@ class Replay extends LitElement
</div> </div>
</div> </div>
</div> </div>
`: ``} `: ""}
</div> </div>
`}`; `}`;
} }
+7 -7
View File
@@ -1,8 +1,8 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss } from './misc'; 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";
// =========================================================================== // ===========================================================================
@@ -16,7 +16,7 @@ class Sorter extends LitElement
this.pageResults = 0; this.pageResults = 0;
this.numResults = 0; this.numResults = 0;
this.sortKey = null this.sortKey = null;
this.sortDesc = null; this.sortDesc = null;
} }
@@ -31,7 +31,7 @@ class Sorter extends LitElement
sortKey: { type: String }, sortKey: { type: String },
sortDesc: { type: Boolean }, sortDesc: { type: Boolean },
} };
} }
firstUpdated() { firstUpdated() {
@@ -126,7 +126,7 @@ class Sorter extends LitElement
`)} `)}
</select> </select>
</div> </div>
<button @click=${(e) => this.sortDesc = !this.sortDesc} class="button is-small"> <button @click=${() => this.sortDesc = !this.sortDesc} class="button is-small">
<span>Order:</span> <span>Order:</span>
<span class="is-sr-only">${this.sortDesc ? "Ascending" : "Descending"}</span> <span class="is-sr-only">${this.sortDesc ? "Ascending" : "Descending"}</span>
<span class="icon"><fa-icon aria-hidden="true" .svg=${this.sortDesc ? fasSortUp : fasSortDown}></span> <span class="icon"><fa-icon aria-hidden="true" .svg=${this.sortDesc ? fasSortUp : fasSortDown}></span>
+18 -16
View File
@@ -1,12 +1,12 @@
import { LitElement, html, css, unsafeCSS } from 'lit-element'; import { LitElement, html, css, unsafeCSS } from "lit-element";
import { wrapCss } from './misc'; import { wrapCss } from "./misc";
import { unsafeHTML } from 'lit-html/directives/unsafe-html'; import { unsafeHTML } from "lit-html/directives/unsafe-html";
import marked from 'marked'; import marked from "marked";
import { getTS, getReplayLink } from './pageutils'; import { getTS, getReplayLink } from "./pageutils";
import Split from 'split.js' import Split from "split.js";
@@ -43,7 +43,7 @@ class Story extends LitElement
isSidebar: { type: Boolean }, isSidebar: { type: Boolean },
splitDirection: { type: Boolean } splitDirection: { type: Boolean }
} };
} }
recalcSplitter(width) { recalcSplitter(width) {
@@ -53,11 +53,11 @@ class Story extends LitElement
firstUpdated() { firstUpdated() {
this.recalcSplitter(document.documentElement.clientWidth); this.recalcSplitter(document.documentElement.clientWidth);
this.obs = new ResizeObserver((entries, observer) => { this.obs = new ResizeObserver((entries/*, observer*/) => {
this.recalcSplitter(entries[0].contentRect.width); this.recalcSplitter(entries[0].contentRect.width);
}); });
this.obs.observe(this) this.obs.observe(this);
} }
updated(changedProperties) { updated(changedProperties) {
@@ -87,7 +87,9 @@ class Story extends LitElement
if (this.splitter) { if (this.splitter) {
try { try {
this.splitter.destroy(); this.splitter.destroy();
} catch (e) {} } catch (e) {
// ignore splitter destory err
}
this.splitter = null; this.splitter = null;
} }
@@ -98,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);
} }
@@ -160,7 +162,7 @@ class Story extends LitElement
width: 100% !important; width: 100% !important;
} }
${Story.sidebarStyles(unsafeCSS(`:host(.sidebar)`))} ${Story.sidebarStyles(unsafeCSS(":host(.sidebar)"))}
.desc p { .desc p {
margin-bottom: 1.0em; margin-bottom: 1.0em;
@@ -257,13 +259,13 @@ 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 href="#list-0" data-list="0" class="${currListNum === 0 ? "is-active" : ""} menu-label is-size-4"
@click=${this.onClickScroll}>${this.collInfo.title}</a> @click=${this.onClickScroll}>${this.collInfo.title}</a>
<ul class="menu-list">${this.collInfo.lists.map(list => html` <ul class="menu-list">${this.collInfo.lists.map(list => html`
<li> <li>
<a @click=${this.onClickScroll} href="#list-${list.id}" <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" : ""}">${list.title}</a>
</li>`)} </li>`)}
</ul> </ul>
</li> </li>
@@ -273,7 +275,7 @@ class Story extends LitElement
<div @scroll=${this.onScroll} class="column main-content main-scroll"> <div @scroll=${this.onScroll} class="column main-content main-scroll">
<section id="list-0" class="desc"> <section id="list-0" class="desc">
<h2 class="has-text-centered title is-3">${this.collInfo.title}</h2> <h2 class="has-text-centered title is-3">${this.collInfo.title}</h2>
${this.collInfo.desc ? unsafeHTML(marked(this.collInfo.desc)) : ''} ${this.collInfo.desc ? unsafeHTML(marked(this.collInfo.desc)) : ""}
</section> </section>
${this.renderLists()} ${this.renderLists()}
</div> </div>
@@ -283,7 +285,7 @@ class Story extends LitElement
renderLists() { renderLists() {
return html` return html`
${this.collInfo.lists.map((list, i) => html` ${this.collInfo.lists.map((list) => html`
<article id="list-${list.id}"> <article id="list-${list.id}">
<div class="content"> <div class="content">
<hr/> <hr/>
+3 -3
View File
@@ -1,6 +1,6 @@
import INDEX_HTML from '../index.html'; import INDEX_HTML from "../index.html";
import { SWReplay } from '@webrecorder/wabac/src/swmain'; import { SWReplay } from "@webrecorder/wabac/src/swmain";
const staticData = new Map(); const staticData = new Map();
@@ -12,5 +12,5 @@ staticData.set(prefix + "index.html", {type: "text/html", content: INDEX_HTML});
//const { SWReplay } = require('@webrecorder/wabac/src/swmain'); //const { SWReplay } = require('@webrecorder/wabac/src/swmain');
self.ipfsCustomPreload = true; self.ipfsCustomPreload = true;
self.sw = new SWReplay(staticData); self.sw = new SWReplay({staticData});
+13 -13
View File
@@ -1,16 +1,16 @@
import { ReplayWebApp } from './appmain'; import { ReplayWebApp } from "./appmain";
import { Chooser } from './chooser'; import { Chooser } from "./chooser";
import { CollIndex, CollInfo } from './coll-index'; import { CollIndex, CollInfo } from "./coll-index";
import { Coll } from './coll'; import { Coll } from "./coll";
import { Story } from './story'; import { Story } from "./story";
import { GDrive } from './gdrive'; import { GDrive } from "./gdrive";
import { Loader } from './loader'; import { Loader } from "./loader";
import { Pages } from './pages'; import { Pages } from "./pages";
import { PageEntry } from './pageentry'; import { PageEntry } from "./pageentry";
import { Replay } from './replay'; import { Replay } from "./replay";
import { Sorter } from './sorter'; 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 { ReplayWebApp, Chooser, CollIndex, CollInfo, Coll, Story, GDrive,
Loader, Pages, PageEntry, Loader, Pages, PageEntry,
+13 -13
View File
@@ -1,9 +1,9 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from "lit-element";
import { wrapCss, clickOnSpacebarPress } from './misc'; import { wrapCss, clickOnSpacebarPress } from "./misc";
import { getReplayLink } from './pageutils'; import { getReplayLink } from "./pageutils";
import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg'; import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import "keyword-mark-element/lib/keyword-mark.js"; import "keyword-mark-element/lib/keyword-mark.js";
@@ -83,7 +83,7 @@ class URLResources extends LitElement
loading: { type: Boolean }, loading: { type: Boolean },
sortKey: { type: String }, sortKey: { type: String },
sortDesc: { type: Boolean } sortDesc: { type: Boolean }
} };
} }
firstUpdated() { firstUpdated() {
@@ -318,7 +318,7 @@ class URLResources extends LitElement
</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 ${this.loading ? "is-loading" : ""}">
<input type="text" class="input" @input="${this.onChangeQuery}" .value="${this.query}" type="text" placeholder="Enter URL to Search"> <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>
@@ -327,9 +327,9 @@ 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}">&nbsp;Contains</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="contains" ?checked="${this.urlSearchType === "contains"}" @click="${this.onClickUrlType}">&nbsp;Contains</label>
<label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === 'prefix'}" @click="${this.onClickUrlType}">&nbsp;Prefix</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="prefix" ?checked="${this.urlSearchType === "prefix"}" @click="${this.onClickUrlType}">&nbsp;Prefix</label>
<label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === 'exact'}" @click="${this.onClickUrlType}">&nbsp;Exact</label> <label class="radio has-text-left"><input type="radio" name="urltype" value="exact" ?checked="${this.urlSearchType === "exact"}" @click="${this.onClickUrlType}">&nbsp;Exact</label>
<span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${this.filteredResults.length} Result(s)</span> <span id="num-results" class="num-results" is-pulled-right" aria-live="polite" aria-atomic="true">${this.filteredResults.length} Result(s)</span>
</div> </div>
</div> </div>
@@ -350,10 +350,10 @@ class URLResources extends LitElement
<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="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="url" class="${this.sortKey === "url" ? (this.sortDesc ? "desc" : "asc") : ""}">URL</a></th>
<th scope="col" class="column col-ts is-2 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="ts" class="${this.sortKey === "ts" ? (this.sortDesc ? "desc" : "asc") : ''}">Date</a></th> <th scope="col" class="column col-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-mime is-3 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="mime" class="${this.sortKey === "mime" ? (this.sortDesc ? "desc" : "asc") : ""}">Mime Type</a></th>
<th scope="col" class="column col-status is-1 is-hidden-mobile"><a role="button" href="#" @click="${this.onSort}" @keyup="${clickOnSpacebarPress}" data-key="status" class="${this.sortKey === "status" ? (this.sortDesc ? "desc" : "asc") : ''}">Status</a></th> <th scope="col" class="column col-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>
+5 -7
View File
File diff suppressed because one or more lines are too long
+101 -74
View File
File diff suppressed because one or more lines are too long
+39 -36
View File
@@ -1,7 +1,9 @@
const path = require('path'); /*eslint-env node */
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const path = require("path");
const CopyPlugin = require('copy-webpack-plugin'); const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
// fake url used in app to serve files // fake url used in app to serve files
// can not use custom scheme due to service worker issues // can not use custom scheme due to service worker issues
@@ -19,15 +21,15 @@ const GDRIVE_CLIENT_ID = "160798412227-tko4c82uopud11q105b2lvbogsj77hlg.apps.goo
const BANNER_TEXT = "'[name].js is part of ReplayWeb.page (https://replayweb.page) Copyright (C) 2020-2021, Webrecorder Software. Licensed under the Affero General Public License v3.'"; const BANNER_TEXT = "'[name].js is part of ReplayWeb.page (https://replayweb.page) Copyright (C) 2020-2021, Webrecorder Software. Licensed under the Affero General Public License v3.'";
// IPFS ipfs-core lib url // IPFS ipfs-core lib url
const IPFS_CORE_URL = "https://cdn.jsdelivr.net/npm/ipfs-core@0.4.2/dist/index.min.js"; const IPFS_CORE_URL = "https://cdn.jsdelivr.net/npm/ipfs-core@0.5.4/dist/index.min.js";
const electronMainConfig = (env, argv) => { const electronMainConfig = (/*env, argv*/) => {
return { return {
target: 'electron-main', target: "electron-main",
mode: 'production', mode: "production",
entry: { entry: {
'electron': './src/electron-main.js', "electron": "./src/electron-main.js",
}, },
resolve: { resolve: {
alias: { alias: {
@@ -37,8 +39,8 @@ const electronMainConfig = (env, argv) => {
} }
}, },
output: { output: {
path: path.join(__dirname, 'dist'), path: path.join(__dirname, "dist"),
filename: '[name].js', filename: "[name].js",
}, },
node: { node: {
__dirname: false, __dirname: false,
@@ -53,8 +55,8 @@ const electronMainConfig = (env, argv) => {
new webpack.BannerPlugin(BANNER_TEXT), new webpack.BannerPlugin(BANNER_TEXT),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: 'node_modules/bcrypto/build/Release/bcrypto.node', to: 'build' }, { from: "node_modules/bcrypto/build/Release/bcrypto.node", to: "build" },
{ from: 'node_modules/leveldown/prebuilds/', to: 'prebuilds' }, { from: "node_modules/leveldown/prebuilds/", to: "prebuilds" },
], ],
}), }),
], ],
@@ -62,16 +64,16 @@ const electronMainConfig = (env, argv) => {
"bufferutil": "bufferutil", "bufferutil": "bufferutil",
"utf-8-validate": "utf-8-validate", "utf-8-validate": "utf-8-validate",
} }
} };
}; };
const electronPreloadConfig = (env, argv) => { const electronPreloadConfig = (/*env, argv*/) => {
return { return {
target: 'electron-preload', target: "electron-preload",
mode: 'production', mode: "production",
entry: { entry: {
'preload': './src/electron-preload.js', "preload": "./src/electron-preload.js",
}, },
plugins: [ plugins: [
new webpack.BannerPlugin(BANNER_TEXT), new webpack.BannerPlugin(BANNER_TEXT),
@@ -82,30 +84,30 @@ const electronPreloadConfig = (env, argv) => {
__IPFS_CORE_URL__: JSON.stringify("") __IPFS_CORE_URL__: JSON.stringify("")
}), }),
] ]
} };
}; };
const browserConfig = (env, argv) => { const browserConfig = (/*env, argv*/) => {
return { return {
target: 'web', target: "web",
mode: 'production', mode: "production",
entry: { entry: {
'ui': './src/ui.js', "ui": "./src/ui.js",
'sw': './src/sw.js' "sw": "./src/sw.js"
}, },
output: { output: {
path: path.join(__dirname), path: path.join(__dirname),
filename: '[name].js', filename: "[name].js",
libraryTarget: 'self', libraryTarget: "self",
globalObject: 'self', globalObject: "self",
publicPath: '/' publicPath: "/"
}, },
externals: { externals: {
electron: 'electron', electron: "electron",
}, },
devServer: { devServer: {
@@ -113,7 +115,7 @@ const browserConfig = (env, argv) => {
port: 9990, port: 9990,
open: false, open: false,
contentBase: path.join(__dirname), contentBase: path.join(__dirname),
publicPath: '/' publicPath: "/"
}, },
plugins: [ plugins: [
@@ -122,7 +124,7 @@ const browserConfig = (env, argv) => {
}), }),
new MiniCssExtractPlugin(), new MiniCssExtractPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__SW_NAME__: JSON.stringify('sw.js'), __SW_NAME__: JSON.stringify("sw.js"),
__APP_FILE_SERVE_PREFIX__ : JSON.stringify(APP_FILE_SERVE_PREFIX), __APP_FILE_SERVE_PREFIX__ : JSON.stringify(APP_FILE_SERVE_PREFIX),
__HELPER_PROXY__ : JSON.stringify(HELPER_PROXY), __HELPER_PROXY__ : JSON.stringify(HELPER_PROXY),
__GDRIVE_CLIENT_ID__ : JSON.stringify(GDRIVE_CLIENT_ID), __GDRIVE_CLIENT_ID__ : JSON.stringify(GDRIVE_CLIENT_ID),
@@ -132,7 +134,8 @@ const browserConfig = (env, argv) => {
new webpack.BannerPlugin(BANNER_TEXT), new webpack.BannerPlugin(BANNER_TEXT),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: 'node_modules/ipfs-core/dist/index.min.js', to: 'ipfs-core.min.js' }, { from: "node_modules/ipfs-core/dist/index.min.js", to: "ipfs-core.min.js" },
{ from: "package.json", to: "_data/package.json" }
] ]
}), }),
], ],
@@ -141,19 +144,19 @@ const browserConfig = (env, argv) => {
rules: [ rules: [
{ {
test: /\.svg$/, test: /\.svg$/,
use: ['svg-inline-loader'], use: ["svg-inline-loader"],
}, },
{ {
test: /main.scss$/, test: /main.scss$/,
use: ['css-loader', 'sass-loader'] use: ["css-loader", "sass-loader"]
}, },
{ {
test: /wombat.js|wombatWorkers.js|index.html$/i, test: /wombat.js|wombatWorkers.js|index.html$/i,
use: ['raw-loader'], use: ["raw-loader"],
} }
] ]
}, },
} };
}; };
module.exports = [ browserConfig, electronMainConfig, electronPreloadConfig ]; module.exports = [ browserConfig, electronMainConfig, electronPreloadConfig ];
+22 -35
View File
@@ -760,12 +760,12 @@
"@webassemblyjs/wast-parser" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@webrecorder/wabac@^2.6.9": "@webrecorder/wabac@^2.7.0":
version "2.6.9" version "2.7.0"
resolved "https://registry.yarnpkg.com/@webrecorder/wabac/-/wabac-2.6.9.tgz#2e453c9b3a71f4b4e20fc79db99243ac4b25716b" resolved "https://registry.yarnpkg.com/@webrecorder/wabac/-/wabac-2.7.0.tgz#d1f54d7faf99aa0333c2ad805b72602dc2b95df1"
integrity sha512-NovRFw02gvjTa/RlAb6zZxmkh1O39Bc8xSXVgY3/w5t7/0JTqih5PNYr1VdbtGqpwO63/Wewp2OFP4JALtcA6Q== integrity sha512-AShk6WfVS3ZYvGfT1ernMAH2uHOemUs62NsiA/oVb6OGwUW0BCVCKkzomyQOLaYxcTqza5nWs73Z+8yVimYq6A==
dependencies: dependencies:
"@webrecorder/wombat" "^3.1.2" "@webrecorder/wombat" "^3.1.4"
brotli "github:foliojs/brotli.js" brotli "github:foliojs/brotli.js"
fast-xml-parser "^3.15.1" fast-xml-parser "^3.15.1"
format-link-header "^3.1.1" format-link-header "^3.1.1"
@@ -779,14 +779,13 @@
parse5-html-rewriting-stream "^6.0.1" parse5-html-rewriting-stream "^6.0.1"
parse5-sax-parser "^6.0.1" parse5-sax-parser "^6.0.1"
path-parser "^6.1.0" path-parser "^6.1.0"
unescape-js "^1.1.4" warcio "^1.4.4"
warcio "^1.4.2"
wbn "^0.0.3" wbn "^0.0.3"
"@webrecorder/wombat@^3.1.2": "@webrecorder/wombat@^3.1.4":
version "3.1.2" version "3.1.4"
resolved "https://registry.yarnpkg.com/@webrecorder/wombat/-/wombat-3.1.2.tgz#2f0477d8b41fdf27ff68a15da54d4e6b5ea14cad" resolved "https://registry.yarnpkg.com/@webrecorder/wombat/-/wombat-3.1.4.tgz#73a1c9d67c3e7d4166819a6c7b72d675b6bd685b"
integrity sha512-2LxW6aD5d1n7APkw3GXNjvwNtSfsiMBEv2iSumfG5z3JA4bqy1DhZL2l/JyTj/GsU1Jr5dKVlDGCEDumrEaflw== integrity sha512-zUpJZ8AMghwUK7dN4C73hKegEEhs4GSoRd6/1ww/kmxUhDrysHH1H5uCESANU/8bulkn4ZQWdN6dxXd3vJgoxg==
"@xtuc/ieee754@^1.2.0": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
@@ -1654,10 +1653,10 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
bulma@^0.9.0: bulma@^0.9.2:
version "0.9.0" version "0.9.2"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.0.tgz#948c5445a49e9d7546f0826cb3820d17178a814f" resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.2.tgz#340011e119c605f19b8ca886bfea595f1deaf23c"
integrity sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ== integrity sha512-e14EF+3VSZ488yL/lJH0tR8mFWiEQVCMi/BQUMi2TGMBOk+zrDg4wryuwm/+dRSHJw0gMawp2tsW7X1JYUCE3A==
bytes@3.0.0: bytes@3.0.0:
version "3.0.0" version "3.0.0"
@@ -2863,10 +2862,10 @@ electron-updater@^4.3.5:
lodash.isequal "^4.5.0" lodash.isequal "^4.5.0"
semver "^7.3.2" semver "^7.3.2"
electron@^11.3.0: electron@^11.4.3:
version "11.3.0" version "11.4.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-11.3.0.tgz#87e8528fd23ae53b0eeb3a738f1fe0a3ad27c2db" resolved "https://registry.yarnpkg.com/electron/-/electron-11.4.3.tgz#74319635417c24fd19e31cfce5a39c8792e4014b"
integrity sha512-MhdS0gok3wZBTscLBbYrOhLaQybCSAfkupazbK1dMP5c+84eVMxJE/QGohiWQkzs0tVFIJsAHyN19YKPbelNrQ== integrity sha512-RhCWJqiYK5oIRGOheilhg/nngCgk0fPgaf00KvbxorlvFZAz8OeMT5ShCpVsMSoyYhk4XEnn4orRly5ltaFYJg==
dependencies: dependencies:
"@electron/get" "^1.0.1" "@electron/get" "^1.0.1"
"@types/node" "^12.0.12" "@types/node" "^12.0.12"
@@ -9118,11 +9117,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
string.fromcodepoint@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz#8d978333c0bc92538f50f383e4888f3e5619d653"
integrity sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM=
string.prototype.trimend@^1.0.0: string.prototype.trimend@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
@@ -9565,13 +9559,6 @@ uint8arrays@^2.0.5:
multibase "^3.0.0" multibase "^3.0.0"
web-encoding "^1.0.5" web-encoding "^1.0.5"
unescape-js@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/unescape-js/-/unescape-js-1.1.4.tgz#4bc6389c499cb055a98364a0b3094e1c3d5da395"
integrity sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==
dependencies:
string.fromcodepoint "^0.2.1"
union-value@^1.0.0: union-value@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -9808,10 +9795,10 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
warcio@^1.4.2: warcio@^1.4.4:
version "1.4.2" version "1.4.4"
resolved "https://registry.yarnpkg.com/warcio/-/warcio-1.4.2.tgz#48ff77a61de6112de8acadf655400074e2c13df8" resolved "https://registry.yarnpkg.com/warcio/-/warcio-1.4.4.tgz#5cd942c7aca0d93b237ef27f9227ef3dec5d18ff"
integrity sha512-trdD7EiPGFsoYuiAC+jzbYQuM/9P2ZK9q7mUc6HAMVkhTsPEluWrgq3q0q3Uv7s3pS345nIt2NU9de/PQNCfLg== integrity sha512-9UyVQoxxy0nRB1HmABeHEL0QMCdyiei8DjzGQxoMuPnzUtiLvtJFuJEJwwaYBNw0L71x2cQJq0l2o2r9A620YQ==
dependencies: dependencies:
"@peculiar/webcrypto" "^1.1.1" "@peculiar/webcrypto" "^1.1.1"
esm "^3.2.25" esm "^3.2.25"