From 1893550274a19fe2a7c765b8a2a41f008af85043 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 16 Aug 2023 18:21:18 -0700 Subject: [PATCH] Format src files with Prettier (#215) Formats files in `src` with [prettier for consistent code style](https://prettier.io/docs/en/why-prettier) and adds npm script Related to https://github.com/webrecorder/replayweb.page/issues/201 --- .eslintrc.js | 17 +- .prettierignore | 4 + package.json | 5 +- src/appmain.js | 520 ++++++++----- src/chooser.js | 217 +++--- src/coll-index.js | 615 +++++++++------- src/coll.js | 1403 +++++++++++++++++++++++------------- src/electron-main.js | 3 +- src/electron-preload.js | 10 +- src/electron-replay-app.js | 166 +++-- src/embed-receipt.js | 338 +++++---- src/embed.js | 78 +- src/gdrive.js | 103 ++- src/loader.js | 281 ++++---- src/misc.js | 165 +++-- src/pageentry.js | 207 ++++-- src/pages.js | 585 +++++++++------ src/pageutils.js | 55 +- src/replay.js | 230 ++++-- src/sorter.js | 41 +- src/story.js | 319 ++++---- src/sw.js | 13 +- src/swmanager.js | 48 +- src/ui.js | 20 +- src/url-resources.js | 391 ++++++---- yarn.lock | 10 + 26 files changed, 3567 insertions(+), 2277 deletions(-) create mode 100644 .prettierignore diff --git a/.eslintrc.js b/.eslintrc.js index 53152e6..dd14bd8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,10 @@ module.exports = { "browser": true, "es6": true }, - "extends": "eslint:recommended", + "extends": [ + "eslint:recommended", + "prettier" + ], "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" @@ -13,21 +16,9 @@ module.exports = { 2, "event", "error" ], - "indent": [ - "error", - 2 - ], "linebreak-style": [ "error", "unix" - ], - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" ] } }; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..99c2a2a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Ignore everything except src +* +!/src/ +!src/* \ No newline at end of file diff --git a/package.json b/package.json index aa8b549..169b894 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,11 @@ "electron-builder": "^23.0.3", "electron-notarize": "^1.1.1", "eslint": "^8.23.1", + "eslint-config-prettier": "^9.0.0", "http-server": "^13.0.2", "mini-css-extract-plugin": "^2.3.0", "node-sass": "^9.0.0", + "prettier": "^3.0.1", "raw-loader": "^4.0.2", "sass-loader": "^13.3.2", "style-loader": "^3.2.1", @@ -67,7 +69,8 @@ "dist": "yarn run build && yarn run pack", "dist-dev": "yarn run build-dev && yarn run pack", "release": "yarn run build && electron-builder", - "lint": "eslint ./src/ webpack.config.js" + "lint": "eslint ./src/ webpack.config.js", + "format": "prettier --write ./src" }, "build": { "afterSign": "build/notarize.js", diff --git a/src/appmain.js b/src/appmain.js index 671d3b7..1260140 100644 --- a/src/appmain.js +++ b/src/appmain.js @@ -1,15 +1,19 @@ import { LitElement, html, css } from "lit"; -import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress } from "./misc"; +import { + wrapCss, + rwpLogo, + IS_APP, + VERSION, + clickOnSpacebarPress, +} from "./misc"; import fasHelp from "@fortawesome/fontawesome-free/svgs/solid/question-circle.svg"; import fasArrowLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg"; import fasArrowRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg"; import { SWManager } from "./swmanager"; - // =========================================================================== -class ReplayWebApp extends LitElement -{ +class ReplayWebApp extends LitElement { // eslint-disable-next-line no-undef constructor(swName = __SW_NAME__) { super(); @@ -104,105 +108,104 @@ class ReplayWebApp extends LitElement static get appStyles() { return css` - #wrlogo { - max-height: 1.0rem; - } - .navbar { - height: 1.5rem; - } - .navbar-brand { - height: 1.5rem; - display: flex; - align-items: center; - } - .wr-logo-item { - padding: 0 8px 0 0; - } - .no-wrap { - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; - } - .has-allcaps { - font-variant-caps: small-caps; - } - :host { - position: fixed; - left: 0px; - top: 0px; - bottom: 0px; - right: 0px; - display: flex; - min-width: 0px; - flex-direction: column; - } - wr-coll { - height: 100%; - } - .navbar { - padding: 0 0.5em; - } - - div.navbar-menu fa-icon { - vertical-align: sub; - } - .tagline { - margin-top: 1.0rem; - } - - .drop-file-overlay { - position: relative; - display: flex; - align-items: center; - justify-content: center; - position: fixed; - inset: 0; - z-index: 50; - font-weight: bold; - font-size: 1.5rem; - background: rgba(255,255,255,.5); - backdrop-filter: blur(2px); - } - - .drop-file-overlay:after { - pointer-events: none; - content: " "; - position: absolute; - inset: 0; - border: 5px dashed #aaa; - margin: 15px; - } - - @media screen and (min-width: 840px) { - .menu-only { - display: none; + #wrlogo { + max-height: 1rem; + } + .navbar { + height: 1.5rem; + } + .navbar-brand { + height: 1.5rem; + display: flex; + align-items: center; + } + .wr-logo-item { + padding: 0 8px 0 0; + } + .no-wrap { + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; + } + .has-allcaps { + font-variant-caps: small-caps; + } + :host { + position: fixed; + left: 0px; + top: 0px; + bottom: 0px; + right: 0px; + display: flex; + min-width: 0px; + flex-direction: column; + } + wr-coll { + height: 100%; + } + .navbar { + padding: 0 0.5em; } - a.arrow-button { - padding-left: 4px; - padding-right: 4px; + div.navbar-menu fa-icon { + vertical-align: sub; + } + .tagline { + margin-top: 1rem; } - .info-menu { - padding: 0 1.0em; + .drop-file-overlay { + position: relative; + display: flex; + align-items: center; + justify-content: center; + position: fixed; + inset: 0; + z-index: 50; + font-weight: bold; + font-size: 1.5rem; + background: rgba(255, 255, 255, 0.5); + backdrop-filter: blur(2px); } - .logo-text { - padding-left: 0px; - margin-left: 6px; + .drop-file-overlay:after { + pointer-events: none; + content: " "; + position: absolute; + inset: 0; + border: 5px dashed #aaa; + margin: 15px; } - a.navbar-item.logo-text:hover { - background-color: initial; - } - } + @media screen and (min-width: 840px) { + .menu-only { + display: none; + } - @media screen and (max-width: 840px) { - .wide-only { - display: none !important; - } - } + a.arrow-button { + padding-left: 4px; + padding-right: 4px; + } + .info-menu { + padding: 0 1em; + } + + .logo-text { + padding-left: 0px; + margin-left: 6px; + } + + a.navbar-item.logo-text:hover { + background-color: initial; + } + } + + @media screen and (max-width: 840px) { + .wide-only { + display: none !important; + } + } `; } @@ -211,8 +214,10 @@ class ReplayWebApp extends LitElement } renderNavBrand() { - return html` - + return html` replay web.page Home @@ -220,88 +225,176 @@ class ReplayWebApp extends LitElement } renderNavBar() { - return html` - - +

Skipped

`; } renderNavEnd() { - return html` - -  User Docs - - - About - `; + About + `; } renderColl() { - return html` - this.showAbout = true}>`; + return html` (this.showAbout = true)} + >`; } renderHomeIndex() { - return html` - - ${!IS_APP ? html` -

Explore and Replay Interactive Archived Webpages Directly in your Browser. (See Examples)

- ` : ""} - + return html` + ${!IS_APP + ? html` +

+ Explore and Replay Interactive Archived Webpages Directly in your + Browser. + (See Examples) +

+ ` + : ""} +
`; } @@ -316,11 +409,8 @@ class ReplayWebApp extends LitElement return html` ${!this.embed || this.embed === "full" ? this.renderNavBar() : ""} - ${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()} - ${this.showAbout ? this.renderAbout() : ""} - ${this.showFileDropOverlay ? this.renderDropFileOverlay() : ""} `; } @@ -331,8 +421,13 @@ class ReplayWebApp extends LitElement // service worker name const name = this.swName + (this.useRuffle ? "?ruffle=1" : ""); - this.swmanager = new SWManager({name, appName: this.appName}); - this.swmanager.register().catch(() => this.swErrorMsg = this.swmanager.renderErrorReport(this.mainLogo)); + this.swmanager = new SWManager({ name, appName: this.appName }); + this.swmanager + .register() + .catch( + () => + (this.swErrorMsg = this.swmanager.renderErrorReport(this.mainLogo)), + ); window.addEventListener("popstate", () => { this.initRoute(); @@ -361,7 +456,7 @@ class ReplayWebApp extends LitElement } } - skipMenu(event){ + skipMenu(event) { // This is a workaround, since this app's routing doesn't permit normal // following of in-page anchors. event.preventDefault(); @@ -377,18 +472,26 @@ class ReplayWebApp extends LitElement // Since this menu can be large and obscure significant page content, // dismiss like a modal, returning keyboard focus to the // menu button on dismissal. - document.addEventListener("click", (event) => { - event.preventDefault(); - this.navMenuShown = false; - this.renderRoot.querySelector("#menu-button").focus(); - }, {once: true}); - document.addEventListener("keypress", (event) => { - if (event.key == "Escape") { + document.addEventListener( + "click", + (event) => { event.preventDefault(); this.navMenuShown = false; this.renderRoot.querySelector("#menu-button").focus(); - } - }, {once: true}); + }, + { once: true }, + ); + document.addEventListener( + "keypress", + (event) => { + if (event.key == "Escape") { + event.preventDefault(); + this.navMenuShown = false; + this.renderRoot.querySelector("#menu-button").focus(); + } + }, + { once: true }, + ); } } @@ -401,7 +504,11 @@ class ReplayWebApp extends LitElement if (state) { try { state = JSON.parse(state); - if ((state.ids instanceof Array) && state.userId && state.action === "open") { + if ( + state.ids instanceof Array && + state.userId && + state.action === "open" + ) { this.pageParams.set("source", "googledrive://" + state.ids[0]); this.pageParams.delete("state"); window.location.search = this.pageParams.toString(); @@ -448,7 +555,9 @@ class ReplayWebApp extends LitElement if (this.pageParams.get("baseUrlSourcePrefix")) { this.loadInfo.extraConfig = this.loadInfo.extraConfig || {}; - this.loadInfo.extraConfig.baseUrlSourcePrefix = this.pageParams.get("baseUrlSourcePrefix"); + this.loadInfo.extraConfig.baseUrlSourcePrefix = this.pageParams.get( + "baseUrlSourcePrefix", + ); } if (this.pageParams.get("basePageUrl")) { @@ -464,7 +573,6 @@ class ReplayWebApp extends LitElement this.loadInfo.noWebWorker = true; } - if (this.pageParams.get("noCache") === "1") { this.loadInfo.noCache = true; } @@ -484,7 +592,7 @@ class ReplayWebApp extends LitElement if (IS_APP && this.sourceUrl.startsWith("file://")) { this.loadInfo = { sourceUrl: this.sourceUrl, - loadUrl: this.sourceUrl.replace("file://", "file2://") + loadUrl: this.sourceUrl.replace("file://", "file2://"), }; } } @@ -501,7 +609,6 @@ class ReplayWebApp extends LitElement window.location.search = this.pageParams.toString(); return; } else { - window.history.pushState({}, "", this.collPageUrl); } @@ -533,7 +640,11 @@ class ReplayWebApp extends LitElement this.pageTitle = event.detail.title; this.pageReplay = event.detail.replayTitle; - document.title = (event.detail.replayTitle ? "Archive of " : "") + this.pageTitle + " | " + this.appName; + document.title = + (event.detail.replayTitle ? "Archive of " : "") + + this.pageTitle + + " | " + + this.appName; } } @@ -561,8 +672,12 @@ class ReplayWebApp extends LitElement diff --git a/src/chooser.js b/src/chooser.js index 8d2a194..ccb0058 100644 --- a/src/chooser.js +++ b/src/chooser.js @@ -3,10 +3,8 @@ import { IS_APP, wrapCss } from "./misc"; import fasUpload from "@fortawesome/fontawesome-free/svgs/solid/upload.svg"; - // =========================================================================== -class Chooser extends LitElement -{ +class Chooser extends LitElement { constructor() { super(); @@ -30,9 +28,9 @@ class Chooser extends LitElement "application/wacz": [".wacz"], "application/wbn": [".wbn"], "application/json": [".json"], - } - } - ] + }, + }, + ], }; } @@ -41,7 +39,7 @@ class Chooser extends LitElement fileDisplayName: { type: String }, droppedFile: { type: File }, newFullImport: { type: Boolean }, - noHead: { type: Boolean } + noHead: { type: Boolean }, }; } @@ -52,13 +50,20 @@ class Chooser extends LitElement } onDropFile() { - const allowedFileExtensions = this.showOpenFilePickerOptions.types.map(type => type.accept).map(Object.values).flat(2); + const allowedFileExtensions = this.showOpenFilePickerOptions.types + .map((type) => type.accept) + .map(Object.values) + .flat(2); - const fileHasAllowedExtension = allowedFileExtensions.some(extension => this.droppedFile.name.endsWith(extension)); + const fileHasAllowedExtension = allowedFileExtensions.some((extension) => + this.droppedFile.name.endsWith(extension), + ); if (fileHasAllowedExtension) { this.setFile(this.droppedFile); - this.dispatchEvent(new CustomEvent("did-drop-file", { bubbles: true, composed: true })); + this.dispatchEvent( + new CustomEvent("did-drop-file", { bubbles: true, composed: true }), + ); this.onStartLoad(); // Automatically load the file } } @@ -81,7 +86,9 @@ class Chooser extends LitElement return; } - const [fileHandle] = await window.showOpenFilePicker(this.showOpenFilePickerOptions); + const [fileHandle] = await window.showOpenFilePicker( + this.showOpenFilePickerOptions, + ); this.fileHandle = fileHandle; this.file = await fileHandle.getFile(); @@ -89,7 +96,10 @@ class Chooser extends LitElement } randomId() { - return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + return ( + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + ); } onStartLoad(event) { @@ -97,7 +107,7 @@ class Chooser extends LitElement event.preventDefault(); } - const loadInfo = {sourceUrl: this.fileDisplayName}; + const loadInfo = { sourceUrl: this.fileDisplayName }; if (this.file) { loadInfo.isFile = true; @@ -108,7 +118,7 @@ class Chooser extends LitElement loadInfo.noCache = true; } else if (this.fileHandle) { loadInfo.loadUrl = this.fileDisplayName; - loadInfo.extra = {fileHandle: this.fileHandle}; + loadInfo.extra = { fileHandle: this.fileHandle }; loadInfo.noCache = false; } else { loadInfo.loadUrl = URL.createObjectURL(this.file); @@ -121,7 +131,13 @@ class Chooser extends LitElement loadInfo.newFullImport = this.newFullImport; - this.dispatchEvent(new CustomEvent("load-start", {bubbles: true, composed: true, detail: loadInfo})); + this.dispatchEvent( + new CustomEvent("load-start", { + bubbles: true, + composed: true, + detail: loadInfo, + }), + ); return false; } @@ -129,7 +145,11 @@ class Chooser extends LitElement onInput(event) { this.fileDisplayName = event.currentTarget.value; - if (this.file && this.fileDisplayName && this.fileDisplayName.startsWith("file://")) { + if ( + this.file && + this.fileDisplayName && + this.fileDisplayName.startsWith("file://") + ) { this.file = null; this.fileDisplayName = ""; } @@ -137,97 +157,122 @@ class Chooser extends LitElement static get styles() { return wrapCss(css` - :host { - min-width: 0; - } - .extra-padding { - padding: 1.5em; - } - .less-padding { - padding-top: 1.0em; - padding-bottom: 1.0em; - } - div.field.has-addons { - flex: auto; - } - .panel-heading { - background-color: #cff3ff; - } - .message-header { - background-color: #cff3ff; - color: black; - } - .heading-size { - font-size: 0.85rem; - } - form { - flex-grow: 1; - flex-shrink: 0; - margin-bottom: 0; - } - p.control.is-expanded { - width: min-content; - } - input.input.file-name:invalid { - border: 1px dashed red; - } - input.input.file-name { - border-width: 1px; - margin-left: -1px; - max-width: 100%; - } - @media screen and (max-width: 1023px) { - .file-icon { - margin-right: 0px; + :host { + min-width: 0; + } + .extra-padding { + padding: 1.5em; + } + .less-padding { + padding-top: 1em; + padding-bottom: 1em; + } + div.field.has-addons { + flex: auto; + } + .panel-heading { + background-color: #cff3ff; + } + .message-header { + background-color: #cff3ff; + color: black; + } + .heading-size { + font-size: 0.85rem; + } + form { + flex-grow: 1; + flex-shrink: 0; + margin-bottom: 0; + } + p.control.is-expanded { + width: min-content; + } + input.input.file-name:invalid { + border: 1px dashed red; + } + input.input.file-name { + border-width: 1px; + margin-left: -1px; + max-width: 100%; + } + @media screen and (max-width: 1023px) { + .file-icon { + margin-right: 0px; + } } - } - @media screen and (max-width: 768px) { - #filename { - border-bottom-right-radius: 4px; - border-top-right-radius: 4px; + @media screen and (max-width: 768px) { + #filename { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + } } - } - `); + `); } render() { - return html` -
+ return html`
-
${this.newFullImport ? "Import Existing" : "Load"} Web Archive
-
+
+ ${this.newFullImport ? "Import Existing" : "Load"} Web Archive +
+

- +

- +
-
diff --git a/src/coll-index.js b/src/coll-index.js index 0d70807..e007015 100644 --- a/src/coll-index.js +++ b/src/coll-index.js @@ -10,10 +10,8 @@ import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double- import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg"; - // =========================================================================== -class CollIndex extends LitElement -{ +class CollIndex extends LitElement { constructor() { super(); @@ -36,17 +34,13 @@ class CollIndex extends LitElement get sortKeys() { return [ - {key: "title", - name: "Title"}, + { key: "title", name: "Title" }, - {key: "sourceUrl", - name: "Source"}, + { key: "sourceUrl", name: "Source" }, - {key: "ctime", - name: this.dateName}, + { key: "ctime", name: this.dateName }, - {key: "size", - name: "Total Size"} + { key: "size", name: "Total Size" }, ]; } @@ -91,10 +85,12 @@ class CollIndex extends LitElement this.filteredColls = []; for (const coll of this.colls) { - if (coll.sourceUrl.indexOf(this.query) >= 0 || - coll.filename.indexOf(this.query) >= 0 || - (coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) || - (coll.title && coll.title.indexOf(this.query) >= 0)) { + if ( + coll.sourceUrl.indexOf(this.query) >= 0 || + coll.filename.indexOf(this.query) >= 0 || + (coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) || + (coll.title && coll.title.indexOf(this.query) >= 0) + ) { this.filteredColls.push(coll); } } @@ -114,7 +110,6 @@ class CollIndex extends LitElement this._deleting = {}; this.sortedColls = []; - } catch (e) { // likely no sw registered yet, or waiting for new sw to register, retry again setTimeout(() => this.loadColls(), 500); @@ -140,7 +135,7 @@ class CollIndex extends LitElement this._deleting[coll.sourceUrl] = true; this.requestUpdate(); - const resp = await fetch(`${apiPrefix}/c/${coll.id}`, {method: "DELETE"}); + const resp = await fetch(`${apiPrefix}/c/${coll.id}`, { method: "DELETE" }); if (resp.status === 200) { const json = await resp.json(); this.colls = json.colls; @@ -154,106 +149,108 @@ class CollIndex extends LitElement static get compStyles() { return css` - :host { - overflow-y: auto; - min-width: 0; - } - .size { - margin-right: 20px; - } - .extra-padding { - padding: 2em; - } - .no-top-padding { - padding-top: 1.0em; - } - .panel-heading { - font-size: 0.85rem; - } - .is-loading { - line-height: 1.5em; - height: 1.5em; - border: 0px; - background-color: transparent !important; - width: auto; - } - div.panel.is-light { - margin-bottom: 2em; - } + :host { + overflow-y: auto; + min-width: 0; + } + .size { + margin-right: 20px; + } + .extra-padding { + padding: 2em; + } + .no-top-padding { + padding-top: 1em; + } + .panel-heading { + font-size: 0.85rem; + } + .is-loading { + line-height: 1.5em; + height: 1.5em; + border: 0px; + background-color: transparent !important; + width: auto; + } + div.panel.is-light { + margin-bottom: 2em; + } - fa-icon { - vertical-align: middle; - } + fa-icon { + vertical-align: middle; + } - .panel-color { - background-color: rgb(210, 249, 214); - } + .panel-color { + background-color: rgb(210, 249, 214); + } - .copy { - color: black; - margin: 0px; - margin: 0; - line-height: 0.4em; - padding: 6px; - border-radius: 10px; - display: none; - position: absolute; - } - .copy:active { - background-color: lightgray; - } - .sort-header { - padding: 0.3rem 0.3rem 0.3rem 0; - display: flex; - flex-direction: row; - flex-flow: row wrap; - } - .sort-header .control { - flex: auto; + .copy { + color: black; + margin: 0px; + margin: 0; + line-height: 0.4em; + padding: 6px; + border-radius: 10px; + display: none; + position: absolute; + } + .copy:active { + background-color: lightgray; + } + .sort-header { + padding: 0.3rem 0.3rem 0.3rem 0; + display: flex; + flex-direction: row; + flex-flow: row wrap; + } + .sort-header .control { + flex: auto; - padding-left: 0.3rem; - width: initial; - } - wr-sorter { - padding: 0.3rem; - } - a.button.is-small.collapse { - border-radius: 6px; - } - .icon.is-left { - margin-left: 0.5rem; - } - .coll-block { - position: relative; - } - .delete-button { - width: 32px; - position: absolute; - top: 10px; - right: 10px; - } - #sort-select::after { - display: none; - } - header { - transform: translate(0px, 0px); - transition: all 0.5s ease 0s; - visibility: visible; - display: flex; - flex-direction: column; - } - header.closed { - transform: translate(0, -100%); - transition: all 0.5s ease 0s; - visibility: visible; - height: 269px; - margin-top: -269px; - } + padding-left: 0.3rem; + width: initial; + } + wr-sorter { + padding: 0.3rem; + } + a.button.is-small.collapse { + border-radius: 6px; + } + .icon.is-left { + margin-left: 0.5rem; + } + .coll-block { + position: relative; + } + .delete-button { + width: 32px; + position: absolute; + top: 10px; + right: 10px; + } + #sort-select::after { + display: none; + } + header { + transform: translate(0px, 0px); + transition: all 0.5s ease 0s; + visibility: visible; + display: flex; + flex-direction: column; + } + header.closed { + transform: translate(0, -100%); + transition: all 0.5s ease 0s; + visibility: visible; + height: 269px; + margin-top: -269px; + } `; } renderHeader() { - return html`

${this.headerName}

`; + return html`

+ ${this.headerName} +

`; } renderSearchHeader() { @@ -264,57 +261,88 @@ class CollIndex extends LitElement const hasHeader = this.childElementCount > 0; return html` -
- -
-
-
- ${hasHeader ? html` - ` : ""} -
-
- ${this.renderHeader()} - ${this.colls.length ? html` -
- ${this.renderSearchHeader()} -
- - -
- - +
+ +
+
+
+ ${hasHeader + ? html` + ` + : ""}
+
+ ${this.renderHeader()} + ${this.colls.length + ? html` +
+ ${this.renderSearchHeader()} +
+ + +
+ + +
-
- ${this.sortedColls && this.sortedColls.map((coll, i) => html` -
- ${this.renderCollInfo(coll)} - ${!this._deleting[coll.sourceUrl] ? html` - - ` : html` +
+ ${this.sortedColls && + this.sortedColls.map( + (coll, i) => html` +
+ ${this.renderCollInfo(coll)} + ${!this._deleting[coll.sourceUrl] + ? html` + + ` + : html` Deleting - `)} +
+ `, + )} +
+ ` + : html` +
+ ${this.sortedColls === null + ? html`Loading Archives...` + : this.renderEmpty()} +
+ `}
- - ` : html` - -
- ${this.sortedColls === null ? html`Loading Archives...` : - this.renderEmpty()} -
- `} -
-
+
`; } @@ -323,14 +351,15 @@ class CollIndex extends LitElement } renderEmpty() { - return html`No Archives so far! Archives loaded in the section above will appear here.`; + return html`No Archives so far! Archives loaded in the section above will appear + here.`; } } - // =========================================================================== -class CollInfo extends LitElement -{ +class CollInfo extends LitElement { constructor() { super(); this.detailed = false; @@ -341,7 +370,7 @@ class CollInfo extends LitElement return { coll: { type: Object }, detailed: { type: Boolean }, - canDelete: { type: Boolean } + canDelete: { type: Boolean }, }; } @@ -351,85 +380,99 @@ class CollInfo extends LitElement static get compStyles() { return css` - .columns { - width: 100%; - } - .column { - word-break: break-word; - position: relative; - } + .columns { + width: 100%; + } + .column { + word-break: break-word; + position: relative; + } - :host { - width: 100%; - height: 100%; - min-width: 0px; - } + :host { + width: 100%; + height: 100%; + min-width: 0px; + } - :host(.is-list) .columns { - display: flex !important; - flex-direction: column; - } + :host(.is-list) .columns { + display: flex !important; + flex-direction: column; + } - :host(.is-list) .column { - width: 100% !important; - } + :host(.is-list) .column { + width: 100% !important; + } - .col-title:hover { - - } - .col-title a { - display: block; - height: 100%; - } - .column:hover > .copy, .source-text:hover + .copy, .copy:hover { - display: inline; - } - .copy { - color: black; - margin: 0px; - margin: 0; - line-height: 0.4em; - padding: 6px; - border-radius: 10px; - display: none; - position: absolute; - } - .copy:active { - background-color: lightgray; - } - .minihead { - font-size: 10px; - font-weight: bold; - } + .col-title:hover { + } + .col-title a { + display: block; + height: 100%; + } + .column:hover > .copy, + .source-text:hover + .copy, + .copy:hover { + display: inline; + } + .copy { + color: black; + margin: 0px; + margin: 0; + line-height: 0.4em; + padding: 6px; + border-radius: 10px; + display: none; + position: absolute; + } + .copy:active { + background-color: lightgray; + } + .minihead { + font-size: 10px; + font-weight: bold; + } `; } renderSource(coll) { return html` -
-

Source

- ${coll.sourceUrl && (coll.sourceUrl.startsWith("http://") || coll.sourceUrl.startsWith("https://")) ? html` - ${coll.sourceUrl} ` : html` - ${coll.sourceUrl} `} -
+
+

Source

+ ${coll.sourceUrl && + (coll.sourceUrl.startsWith("http://") || + coll.sourceUrl.startsWith("https://")) + ? html` ${coll.sourceUrl} ` + : html` ${coll.sourceUrl} `} +
- - ${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html` -

(${coll.filename})

` : ""} -
-

Date Loaded

${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}
-

Total Size

${prettyBytes(Number(coll.size || 0))}
- `; + + ${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") + ? html`

(${coll.filename})

` + : ""} +
+
+

Date Loaded

+ ${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""} +
+
+

Total Size

+ ${prettyBytes(Number(coll.size || 0))} +
+ `; } renderSummaryView() { const coll = this.coll; - return html` -
+ return html`
${this.renderSource(coll)} @@ -439,67 +482,91 @@ class CollInfo extends LitElement renderDetailed() { const coll = this.coll; - let {numValid, numInvalid, domain, certFingerprint, datapackageHash, publicKey, software} = this.coll.verify || {}; + let { + numValid, + numInvalid, + domain, + certFingerprint, + datapackageHash, + publicKey, + software, + } = this.coll.verify || {}; numValid = numValid || 0; numInvalid = numInvalid || 0; - const certFingerprintUrl = certFingerprint ? `https://crt.sh/?q=${certFingerprint}` : ""; + const certFingerprintUrl = certFingerprint + ? `https://crt.sh/?q=${certFingerprint}` + : ""; - return html` -
-
- - ${coll.title || coll.filename} - -
- ${coll.desc ? html` -
+ return html`
+
+ + ${coll.title || coll.filename} + +
+ ${coll.desc + ? html`

Description

${coll.desc} -
` : html` - `} -

Filename

${coll.filename}
- ${this.renderSource(coll)} +
` + : html``} +
+

Filename

+ ${coll.filename} +
+ ${this.renderSource(coll)} + ${domain + ? html` +
+

Observed By

+

${domain}

+ ${certFingerprintUrl + ? html`View Certificate` + : ""} +
+ ` + : software + ? html` +
+

Created With

+ ${software || "Unknown"} +
+ ` + : ""} - ${domain ? html` -
-

Observed By

-

${domain}

- ${certFingerprintUrl ? html`View Certificate` : ""} -
- ` : software ? html` -
-

Created With

- ${software || "Unknown"} -
- ` : ""} +
+

Validation

+ ${numValid > 0 || numInvalid > 0 + ? html`

+ ${numValid} hashes + verified${numInvalid ? html`, ${numInvalid} invalid` : ""} +

` + : html` Not Available`} +
-
-

Validation

- ${numValid > 0 || numInvalid > 0 ? html` -

${numValid} hashes verified${numInvalid ? html`, ${numInvalid} invalid` : ""}

` : html` - Not Available`} -
- -
-

Package Hash

+
+

Package Hash

${datapackageHash || "Not Available"} -
+
-
-

Observer Public Key

+
+

Observer Public Key

${publicKey || "Not Available"} -
+
-
-

Loading Mode

- ${coll.onDemand ? "Download On-Demand" : "Fully Local"} -
-
-

Collection id

- ${coll.coll} -
-
`; +
+

Loading Mode

+ ${coll.onDemand ? "Download On-Demand" : "Fully Local"} +
+
+

Collection id

+ ${coll.coll} +
+
`; } render() { @@ -514,8 +581,8 @@ class CollInfo extends LitElement } onPurge(reload) { - const detail = {reload}; - this.dispatchEvent(new CustomEvent("coll-purge", {detail})); + const detail = { reload }; + this.dispatchEvent(new CustomEvent("coll-purge", { detail })); } } diff --git a/src/coll.js b/src/coll.js index 8b01a2c..d346e9a 100644 --- a/src/coll.js +++ b/src/coll.js @@ -1,5 +1,13 @@ import { LitElement, html, css } from "lit"; -import { wrapCss, rwpLogo, IS_APP, VERSION, clickOnSpacebarPress, apiPrefix, replayPrefix } from "./misc"; +import { + wrapCss, + rwpLogo, + IS_APP, + VERSION, + clickOnSpacebarPress, + apiPrefix, + replayPrefix, +} from "./misc"; import { sourceToId, tsToDate, getPageDateTS } from "./pageutils"; @@ -27,13 +35,10 @@ import fasAngleRight from "@fortawesome/fontawesome-free/svgs/solid/angle-right. import { RWPEmbedReceipt } from "./embed-receipt.js"; import Split from "split.js"; - const RWP_SCHEME = "search://"; - // =========================================================================== -class Coll extends LitElement -{ +class Coll extends LitElement { constructor() { super(); this.sourceUrl = null; @@ -55,10 +60,10 @@ class Coll extends LitElement this.tabNames = ["pages", "story", "resources", "info"]; this.tabLabels = { - "pages": "Pages", - "story": "Story", - "resources": "URLs", - "info": "Archive Info", + pages: "Pages", + story: "Story", + resources: "URLs", + info: "Archive Info", }; this.menuActive = false; @@ -117,7 +122,7 @@ class Coll extends LitElement isVisible: { type: Boolean }, - favIconUrl: {type: String }, + favIconUrl: { type: String }, appName: { type: String }, appVersion: { type: String }, @@ -125,7 +130,7 @@ class Coll extends LitElement autoUpdateInterval: { type: Number }, - swName: { type: String } + swName: { type: String }, }; } @@ -138,7 +143,7 @@ class Coll extends LitElement }); if (this.embed && this.loadInfo && this.loadInfo.hideOffscreen) { - this.observer = new IntersectionObserver((entries/*, observer*/) => { + this.observer = new IntersectionObserver((entries /*, observer*/) => { this.isVisible = entries[0].isIntersecting; }); @@ -149,8 +154,16 @@ class Coll extends LitElement async runUpdateLoop() { try { // only autoupdate if interval is set, and number of pages < 100 to avoid messing up scrolling - while (this.editable && this.autoUpdateInterval && (!this.collInfo || !this.collInfo.pages || this.collInfo.pages.length < 100)) { - await new Promise(resolve => setTimeout(resolve, this.autoUpdateInterval * 1000)); + while ( + this.editable && + this.autoUpdateInterval && + (!this.collInfo || + !this.collInfo.pages || + this.collInfo.pages.length < 100) + ) { + await new Promise((resolve) => + setTimeout(resolve, this.autoUpdateInterval * 1000), + ); await this.doUpdateInfo(true); } } finally { @@ -181,17 +194,22 @@ class Coll extends LitElement } // don't add empty params to shorten query - Object.keys(this.tabData). - forEach(key => !this.tabData[key] && delete this.tabData[key]); + Object.keys(this.tabData).forEach( + (key) => !this.tabData[key] && delete this.tabData[key], + ); const newHash = "#" + new URLSearchParams(this.tabData).toString(); if (!this.tabData.url) { - this.url = RWP_SCHEME + decodeURIComponent(this._paramsToString(this.tabData)); + this.url = + RWP_SCHEME + decodeURIComponent(this._paramsToString(this.tabData)); } if (newHash !== this._locationHash) { this._locationHash = newHash; - if (this._replaceLoc || Object.keys(changedProperties.get("tabData")).length === 0) { + if ( + this._replaceLoc || + Object.keys(changedProperties.get("tabData")).length === 0 + ) { const newLoc = new URL(window.location.href); newLoc.hash = this._locationHash; window.history.replaceState({}, "", newLoc.href); @@ -217,7 +235,10 @@ class Coll extends LitElement } } - if (changedProperties.has("tabData") || changedProperties.has("showSidebar")) { + if ( + changedProperties.has("tabData") || + changedProperties.has("showSidebar") + ) { this.configureSplitter(); } } @@ -241,7 +262,7 @@ class Coll extends LitElement onDragEnd() { replay.setDisablePointer(false); - } + }, }; this.splitter = Split([contents, replay], opts); @@ -256,8 +277,7 @@ class Coll extends LitElement } } - async doUpdateInfo(autorefresh = false) - { + async doUpdateInfo(autorefresh = false) { // if auto-refresh, and replay and no sidebar, than skip update if (autorefresh && this.tabData.url && !this.showSidebar) { return; @@ -288,14 +308,18 @@ class Coll extends LitElement apiPrefix: collApiPrefix, replayPrefix: collReplayPrefix, coll, - ...json + ...json, }; - if (this.loadInfo && this.loadInfo.extraConfig && this.loadInfo.extraConfig.headers) { + if ( + this.loadInfo && + this.loadInfo.extraConfig && + this.loadInfo.extraConfig.headers + ) { const headers = this.loadInfo.extraConfig.headers; await fetch(`${collApiPrefix}/updateAuth`, { method: "POST", - body: JSON.stringify({headers}) + body: JSON.stringify({ headers }), }); } @@ -309,10 +333,14 @@ class Coll extends LitElement this.hasStory = this.collInfo.desc || this.collInfo.lists.length; - this.dispatchEvent(new CustomEvent("coll-loaded", {detail: { - collInfo: this.collInfo, - alreadyLoaded: true - }})); + this.dispatchEvent( + new CustomEvent("coll-loaded", { + detail: { + collInfo: this.collInfo, + alreadyLoaded: true, + }, + }), + ); this.onHashChange(); } @@ -323,10 +351,14 @@ class Coll extends LitElement if (event.detail.sourceUrl) { this.sourceUrl = event.detail.sourceUrl; } - this.dispatchEvent(new CustomEvent("coll-loaded", {detail: { - sourceUrl: this.sourceUrl, - collInfo: this.collInfo, - }})); + this.dispatchEvent( + new CustomEvent("coll-loaded", { + detail: { + sourceUrl: this.sourceUrl, + collInfo: this.collInfo, + }, + }), + ); } onCollUpdate(event) { @@ -334,21 +366,28 @@ class Coll extends LitElement return; } - this.collInfo = {...this.collInfo, ...event.detail}; + this.collInfo = { ...this.collInfo, ...event.detail }; } onHashChange() { const hash = window.location.hash; 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(), + ); this._locationHash = hash; } if (this.collInfo.coll && !this.tabNames.includes(this.tabData.view)) { - const view = this.hasStory ? "story" : (this.editable || this.collInfo.pages.length ? "pages" : "resources"); + const view = this.hasStory + ? "story" + : this.editable || this.collInfo.pages.length + ? "pages" + : "resources"; this.tabData = { - ...this.tabData, view + ...this.tabData, + view, }; } @@ -373,7 +412,7 @@ class Coll extends LitElement onTabClick(event) { event.preventDefault(); const hash = event.currentTarget.getAttribute("href"); - this.tabData = {...this.tabData, view: hash.slice(1)}; + this.tabData = { ...this.tabData, view: hash.slice(1) }; //this.tabData = {view: hash.slice(1)}; return false; } @@ -384,15 +423,18 @@ class Coll extends LitElement 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); } else if (this.showSidebar && this.tabData.url) { this.updateTabData(event.detail.data, event.detail.replaceLoc, true); } } - updateTabData(data, replaceLoc = false/*, merge = false*/) { - this.tabData = {...this.tabData, ...data}; + updateTabData(data, replaceLoc = false /*, merge = false*/) { + this.tabData = { ...this.tabData, ...data }; if (this.tabData.url) { this.url = this.tabData.url || ""; } @@ -409,257 +451,264 @@ class Coll extends LitElement static get compStyles() { return css` - :host { - display: flex; - flex-direction: column; - height: 100%; - min-width: 0px; - } + :host { + display: flex; + flex-direction: column; + height: 100%; + min-width: 0px; + } - .icon { - vertical-align: text-top; - } + .icon { + vertical-align: text-top; + } - .back fa-icon { - width: 1.5em; - vertical-align: bottom; - line-height: 0.5em; - } + .back fa-icon { + width: 1.5em; + vertical-align: bottom; + line-height: 0.5em; + } - li.is-active { - font-weight: bold; - } + li.is-active { + font-weight: bold; + } - .tab-label { - display: inline; - } - - @media screen and (max-width: ${!IS_APP ? css`1053px` : css`1163px`}) { .tab-label { - display: none; + display: inline; } - .main.tabs span.icon { - margin: 0px; + @media screen and (max-width: ${!IS_APP ? css`1053px` : css`1163px`}) { + .tab-label { + display: none; + } + + .main.tabs span.icon { + margin: 0px; + } } - } - .main.tabs { - display: flex; - flex-direction: row; - margin-bottom: 0px; - } - - .main.tabs ul { - position: relative; - } - - .main.tabs li { - line-height: 1.5; - padding: 8px 0 6px 0; - } - - @media screen and (max-width: 319px) { - .main.tabs li a { - padding-right: 4px; - padding-left: 4px; + .main.tabs { + display: flex; + flex-direction: row; + margin-bottom: 0px; } - } - .sidebar.main.tabs li a { - padding-right: 6px; - padding-left: 6px; - } + .main.tabs ul { + position: relative; + } - #contents { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - min-height: 0px; - flex: auto; - background-color: white; - } + .main.tabs li { + line-height: 1.5; + padding: 8px 0 6px 0; + } - #tabContents { - height: 100%; - width: 100%; - display: flex; - flex-direction: row; - min-height: 0px; - flex: auto; - } + @media screen and (max-width: 319px) { + .main.tabs li a { + padding-right: 4px; + padding-left: 4px; + } + } - rwp-embed-receipt { - flex-direction: column; - display: flex; - } + .sidebar.main.tabs li a { + padding-right: 6px; + padding-left: 6px; + } - ${RWPEmbedReceipt.embedStyles} + #contents { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + min-height: 0px; + flex: auto; + background-color: white; + } - ${Coll.replayBarStyles}`; + #tabContents { + height: 100%; + width: 100%; + display: flex; + flex-direction: row; + min-height: 0px; + flex: auto; + } + + rwp-embed-receipt { + flex-direction: column; + display: flex; + } + + ${RWPEmbedReceipt.embedStyles} + + ${Coll.replayBarStyles} + `; } static get replayBarStyles() { return css` - .breadbar { - display: flex; - align-items: center; - height: 35px; - width: 100%; - background-color: aliceblue; - padding: 0.5em; - } + .breadbar { + display: flex; + align-items: center; + height: 35px; + width: 100%; + background-color: aliceblue; + padding: 0.5em; + } - .replay-bar { - padding: 0.5em 0em 0.5em 0.5em; - max-width: none; - border-bottom: solid .1rem #97989A; - width: 100%; - background-color: white; - } + .replay-bar { + padding: 0.5em 0em 0.5em 0.5em; + max-width: none; + border-bottom: solid 0.1rem #97989a; + width: 100%; + background-color: white; + } - input#url { - border-radius: 4px; - } + input#url { + border-radius: 4px; + } - .favicon img { - width: 20px; - height: 20px; - margin: 8px; - /*filter: drop-shadow(1px 1px 2px grey);*/ - } + .favicon img { + width: 20px; + height: 20px; + margin: 8px; + /*filter: drop-shadow(1px 1px 2px grey);*/ + } - #datetime { - position: absolute; - right: 1em; - z-index: 10; - background: linear-gradient(90deg, rgba(255, 255, 255, 0), #FFF 15%, #FFF); - margin: -35px 0 0 0px; - padding-left: 3em; - line-height: 2; - } + #datetime { + position: absolute; + right: 1em; + z-index: 10; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0), + #fff 15%, + #fff + ); + margin: -35px 0 0 0px; + padding-left: 3em; + line-height: 2; + } - .menu-head { - font-size: 10px; - font-weight: bold; - display: block; - } - .menu-logo { - vertical-align: middle; - } - .menu-version { - font-size: 10px; - } - .dropdown-item.info { - font-style: italic; - } + .menu-head { + font-size: 10px; + font-weight: bold; + display: block; + } + .menu-logo { + vertical-align: middle; + } + .menu-version { + font-size: 10px; + } + .dropdown-item.info { + font-style: italic; + } - input:focus + #datetime { - display: none; - } + input:focus + #datetime { + display: none; + } - .grey-disabled { - --fa-icon-fill-color: lightgrey; - color: lightgrey; - } + .grey-disabled { + --fa-icon-fill-color: lightgrey; + color: lightgrey; + } - .replay-bar .button:focus { - box-shadow: none; - } + .replay-bar .button:focus { + box-shadow: none; + } - .is-borderless { - border: 0px; - } + .is-borderless { + border: 0px; + } - .narrow { - padding: calc(0.5em - 1px) 0.8em; - } + .narrow { + padding: calc(0.5em - 1px) 0.8em; + } - form { - width: 100%; - margin: 0 0 0 0.5em; - } + form { + width: 100%; + margin: 0 0 0 0.5em; + } - .gutter.gutter-horizontal { - cursor: col-resize; - float: left; - background-color: rgb(151, 152, 154); - } + .gutter.gutter-horizontal { + cursor: col-resize; + float: left; + background-color: rgb(151, 152, 154); + } - .gutter.gutter-horizontal:hover { - cursor: col-resize; - } + .gutter.gutter-horizontal:hover { + cursor: col-resize; + } - main, wr-coll-replay { - width: 100%; - } + main, + wr-coll-replay { + width: 100%; + } - .info-bg { - background-color: whitesmoke; - width: 100%; - height: 100%; - display: flex; - overflow: auto; - } + .info-bg { + background-color: whitesmoke; + width: 100%; + height: 100%; + display: flex; + overflow: auto; + } - .is-list { - margin: 1.0em; - background-color: whitesmoke; - height: fit-content; - } + .is-list { + margin: 1em; + background-color: whitesmoke; + height: fit-content; + } - #contents.full-pages { - width: 100% !important; - } + #contents.full-pages { + width: 100% !important; + } - .sidebar-nav { - position: absolute; - vertical-align: middle; - } + .sidebar-nav { + position: absolute; + vertical-align: middle; + } - .sidebar-nav a { - display: inline-block; - border-bottom: 0px; - } + .sidebar-nav a { + display: inline-block; + border-bottom: 0px; + } - .sidebar-nav span.nav-hover { - font-size: smaller; - display: none; - } + .sidebar-nav span.nav-hover { + font-size: smaller; + display: none; + } - .sidebar-nav:hover span.nav-hover, - .sidebar-nav:focus-within span.nav-hover { - display: initial; - color: rgb(72, 118, 255); - } + .sidebar-nav:hover span.nav-hover, + .sidebar-nav:focus-within span.nav-hover { + display: initial; + color: rgb(72, 118, 255); + } - .sidebar-nav fa-icon { - vertical-align: bottom; - } + .sidebar-nav fa-icon { + vertical-align: bottom; + } - .sidebar-nav:hover fa-icon { - color: rgb(72, 118, 255); - } + .sidebar-nav:hover fa-icon { + color: rgb(72, 118, 255); + } - .sidebar-nav.left { - left: 8px; - } + .sidebar-nav.left { + left: 8px; + } - .sidebar-nav.right { - right: 8px; - } + .sidebar-nav.right { + right: 8px; + } - /* Since the replay sometimes programmatically receives keyboard focus, + /* Since the replay sometimes programmatically receives keyboard focus, and that is visually unexpected for mouse-users, and since this won't particularly trip up keyboard users, just remove the focus style. */ - wr-coll-replay:focus { - outline: none; - } - /* Some keyboard-users may see this replacement style */ - wr-coll-replay:focus-visible { - outline: 1px solid rgb(72, 118, 255); - } + wr-coll-replay:focus { + outline: none; + } + /* Some keyboard-users may see this replacement style */ + wr-coll-replay:focus-visible { + outline: 1px solid rgb(72, 118, 255); + } `; } @@ -669,41 +718,66 @@ class Coll extends LitElement const isReplay = !!this.tabData.url; const isSidebar = isReplay && this.showSidebar; - if (!isReplay && this.tabData && this.tabData.view){ - const detail = {title: this.tabLabels[this.tabData.view], replayTitle: false}; - this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail})); + if (!isReplay && this.tabData && this.tabData.view) { + const detail = { + title: this.tabLabels[this.tabData.view], + replayTitle: false, + }; + this.dispatchEvent( + new CustomEvent("update-title", { + bubbles: true, + composed: true, + detail, + }), + ); } if (this.collInfo && !this.collInfo.coll) { - return html` - `; + return html` `; } else if (this.collInfo) { return html` - ${this.renderLocationBar()} - ${this.renderVerifyInfo()} -
-
- ${this.renderTabHeader(isSidebar)} - ${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``} -
- - ${isReplay && this.isVisible ? html` - +
- - ` : ""} -
+ ${this.renderTabHeader(isSidebar)} + ${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``} +
+ + ${isReplay && this.isVisible + ? html` + + + ` + : ""} +
`; } else { return html``; @@ -715,57 +789,149 @@ class Coll extends LitElement // return ""; // } - return html` - `; } renderLocationBar() { @@ -779,130 +945,330 @@ class Coll extends LitElement const showFavIcon = isReplay && this.favIconUrl; - return html` - - +

Skipped

`; } renderVerifyInfo() { @@ -911,12 +1277,12 @@ class Coll extends LitElement } return html` - `; + .collInfo=${this.collInfo} + url=${this.url} + ts=${this.ts} + .appLogo=${this.appLogo} + > + `; } dragStart() { @@ -934,14 +1300,13 @@ class Coll extends LitElement } renderCollInfo() { - return html` -
+ return html`
`; } @@ -957,53 +1322,59 @@ class Coll extends LitElement const isInfo = this.tabData.view === "info"; return html` - - ${isInfo ? this.renderCollInfo() : html``} - - ${isStory ? html` - - ` : ""} - - ${isResources ? html` - - ` : ""} - - ${isPages ? html` - - ` : ""} + ${isInfo ? this.renderCollInfo() : html``} + ${isStory + ? html` + ` + : ""} + ${isResources + ? html` + ` + : ""} + ${isPages + ? html` + ` + : ""} `; } - skipMenu(event){ + skipMenu(event) { // This is a workaround, since this app's routing doesn't permit normal // following of in-page anchors. event.preventDefault(); @@ -1022,9 +1393,13 @@ class Coll extends LitElement this.menuActive = !this.menuActive; if (this.menuActive) { - document.addEventListener("click", () => { - this.menuActive = false; - }, {once: true}); + document.addEventListener( + "click", + () => { + this.menuActive = false; + }, + { once: true }, + ); } } @@ -1053,18 +1428,18 @@ class Coll extends LitElement onShowPages(event) { event.preventDefault(); // show sidebar for tablet or wider, or hide sidebar - if (this.showSidebar || (document.documentElement.clientWidth >= 769)) { + if (this.showSidebar || document.documentElement.clientWidth >= 769) { this.showSidebar = !this.showSidebar; } else { // otherwise, just go to full pages view this.showSidebar = false; - this.updateTabData({url: "", ts: ""}); + this.updateTabData({ url: "", ts: "" }); } } onFullPageView(event) { event.preventDefault(); - this.updateTabData({url: "", ts: ""}); + this.updateTabData({ url: "", ts: "" }); } onHideSidebar(event) { @@ -1091,7 +1466,10 @@ class Coll extends LitElement onPurgeCache(event) { event.preventDefault(); - const reload = event.detail && event.detail.reload !== undefined ? event.detail.reload : true; + const reload = + event.detail && event.detail.reload !== undefined + ? event.detail.reload + : true; this.deleteFully(reload); } @@ -1137,7 +1515,7 @@ class Coll extends LitElement let data; if (value.startsWith("http://") || value.startsWith("https://")) { - data = {url: value}; + data = { url: value }; if (value === this.tabData.url) { const replay = this.renderRoot.querySelector("wr-coll-replay"); @@ -1146,10 +1524,9 @@ class Coll extends LitElement } return; } - } else { if (!value.startsWith(RWP_SCHEME)) { - data = {query: value, view: "pages"}; + data = { query: value, view: "pages" }; } else { data = this._stringToParams(value); } @@ -1163,7 +1540,13 @@ class Coll extends LitElement data.url = ""; data.ts = ""; - for (const param of ["query", "view", "currList", "currMime", "urlSearchType"]) { + for (const param of [ + "query", + "view", + "currList", + "currMime", + "urlSearchType", + ]) { if (q.has(param)) { data[param] = q.get(param); } @@ -1175,7 +1558,13 @@ class Coll extends LitElement _paramsToString(value) { const q = new URLSearchParams(); - for (const param of ["query", "view", "currList", "currMime", "urlSearchType"]) { + for (const param of [ + "query", + "view", + "currList", + "currMime", + "urlSearchType", + ]) { if (param in value) { q.set(param, value[param]); } diff --git a/src/electron-main.js b/src/electron-main.js index 34d0b14..284c035 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -3,11 +3,10 @@ import { ElectronReplayApp } from "./electron-replay-app"; import path from "path"; - // ============================================================================ const replayApp = new ElectronReplayApp({ staticPath: path.join(__dirname, "../"), - profileName: "replaywebpage" + profileName: "replaywebpage", }); replayApp.init(true); diff --git a/src/electron-preload.js b/src/electron-preload.js index 80ab184..6cac296 100644 --- a/src/electron-preload.js +++ b/src/electron-preload.js @@ -4,7 +4,7 @@ import { CollectionLoader } from "@webrecorder/wabac/src/loaders"; const { ipcRenderer, contextBridge } = require("electron"); -contextBridge.exposeInMainWorld("electron", {"IS_APP": true}); +contextBridge.exposeInMainWorld("electron", { IS_APP: true }); const dbs = {}; @@ -27,9 +27,9 @@ async function getDB(name) { async function getResponse(event, request, coll, ts, channel) { const db = await getDB(coll); - const req = {request, url: request.url, timestamp: ts}; + const req = { request, url: request.url, timestamp: ts }; - const result = await db.getResource(req, "", {request}); + const result = await db.getResource(req, "", { request }); if (!result) { ipcRenderer.send(channel, 404); @@ -46,7 +46,3 @@ async function getResponse(event, request, coll, ts, channel) { ipcRenderer.on("getresponse", getResponse); export { getDB, getColl, loader }; - - - - diff --git a/src/electron-replay-app.js b/src/electron-replay-app.js index 1c174ac..c3487b3 100644 --- a/src/electron-replay-app.js +++ b/src/electron-replay-app.js @@ -3,7 +3,15 @@ import fetch from "node-fetch"; import { Headers } from "node-fetch"; -import {app, session, BrowserWindow, ipcMain, protocol, screen, shell } from "electron"; +import { + app, + session, + BrowserWindow, + ipcMain, + protocol, + screen, + shell, +} from "electron"; import path from "path"; import fs from "fs"; @@ -30,9 +38,8 @@ const FILE_PROTO = "file2"; const URL_RX = /([^/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/; // ============================================================================ -class ElectronReplayApp -{ - constructor({staticPath="./", profileName=""} = {}) { +class ElectronReplayApp { + constructor({ staticPath = "./", profileName = "" } = {}) { this.pluginPath = ""; this.appPath = app.getAppPath(); @@ -50,7 +57,7 @@ class ElectronReplayApp this.openNextFile = null; - this.screenSize = {width: 1024, height: 768}; + this.screenSize = { width: 1024, height: 768 }; this.origUA = null; } @@ -76,10 +83,12 @@ class ElectronReplayApp const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { - console.log("App already running, opening new window in first instance and quitting"); + console.log( + "App already running, opening new window in first instance and quitting", + ); app.quit(); } else { - app.on("second-instance", (event, commandLine/*, workingDir*/) => { + app.on("second-instance", (event, commandLine /*, workingDir*/) => { // Just create a new window in case of second instance request this.createMainWindow(commandLine); }); @@ -93,21 +102,26 @@ class ElectronReplayApp console.log("user data", app.getPath("userData")); if (this.profileName) { - app.setPath("userData", path.join(app.getPath("appData"), this.profileName)); + app.setPath( + "userData", + path.join(app.getPath("appData"), this.profileName), + ); } - protocol.registerSchemesAsPrivileged([{ - scheme: FILE_PROTO, - privileges: { - standard: false, - secure: true, - bypassCSP: true, - allowServiceWorkers: true, - supportFetchAPI: true, - corsEnabled: true, - stream: true - } - }]); + protocol.registerSchemesAsPrivileged([ + { + scheme: FILE_PROTO, + privileges: { + standard: false, + secure: true, + bypassCSP: true, + allowServiceWorkers: true, + supportFetchAPI: true, + corsEnabled: true, + stream: true, + }, + }, + ]); app.on("will-finish-launching", () => { app.on("open-file", (event, filePath) => { @@ -149,28 +163,32 @@ class ElectronReplayApp this.screenSize = screen.getPrimaryDisplay().workAreaSize; app.on("web-contents-created", (event, contents) => { - contents.setWindowOpenHandler(({url}) => { + contents.setWindowOpenHandler(({ url }) => { // load docs in native browser for now if (url === STATIC_PREFIX + "docs") { shell.openExternal("https://replayweb.page/docs/"); - return { action: "deny"}; + return { action: "deny" }; } // load external URLs in native browser if (!url.startsWith(STATIC_PREFIX)) { shell.openExternal(url); - return { action: "deny"}; + return { action: "deny" }; } - return { action: "allow"}; + return { action: "allow" }; }); }); const sesh = session.defaultSession; - sesh.protocol.interceptStreamProtocol("http", (request, callback) => this.doIntercept(request, callback)); + sesh.protocol.interceptStreamProtocol("http", (request, callback) => + this.doIntercept(request, callback), + ); - protocol.registerStreamProtocol(FILE_PROTO, (request, callback) => this.doHandleFile(request, callback)); + protocol.registerStreamProtocol(FILE_PROTO, (request, callback) => + this.doHandleFile(request, callback), + ); this.origUA = sesh.getUserAgent(); @@ -182,13 +200,13 @@ class ElectronReplayApp //const filename = parsedUrl.searchParams.get("filename"); if (request.url === FILE_PROTO + "://localhost") { - callback({statusCode: 200, data: null}); + callback({ statusCode: 200, data: null }); return; } const filename = url.fileURLToPath(request.url.replace(FILE_PROTO, "file")); - const headers = {"Content-Type": "application/octet-stream"}; + const headers = { "Content-Type": "application/octet-stream" }; const reqHeaders = new Headers(request.headers); if (filename) { @@ -200,13 +218,19 @@ class ElectronReplayApp const size = stat.size; - const {statusCode, start, end} = this.parseRange(reqHeaders, headers, size); + const { statusCode, start, end } = this.parseRange( + reqHeaders, + headers, + size, + ); - const data = request.method === "HEAD" ? null : fs.createReadStream(filename, {start, end}); + const data = + request.method === "HEAD" + ? null + : fs.createReadStream(filename, { start, end }); - callback({statusCode, headers, data}); + callback({ statusCode, headers, data }); return; - } else { return this.notFound("No Resource Specified", callback); } @@ -235,7 +259,6 @@ class ElectronReplayApp return await this.resolveArchiveResponse(request, callback); } } else { - // try serve static file from app dir let filename = request.url.slice(STATIC_PREFIX.length).split("?", 1)[0]; filename = filename.split("#", 1)[0]; @@ -261,10 +284,14 @@ class ElectronReplayApp const data = fs.createReadStream(fullPath); - return callback({statusCode: 200, headers: {"content-type": mimeType}, data}); + return callback({ + statusCode: 200, + headers: { "content-type": mimeType }, + data, + }); } } - + return this.notFound(request.url, callback); } @@ -278,9 +305,11 @@ class ElectronReplayApp async proxyLive(request, callback) { let headers = request.headers; - const {method, url, uploadData} = request; + const { method, url, uploadData } = request; - const body = uploadData ? Readable.from(readBody(uploadData, session.defaultSession)) : null; + const body = uploadData + ? Readable.from(readBody(uploadData, session.defaultSession)) + : null; if (this.origUA) { // pass UA if origUA is set @@ -290,23 +319,29 @@ class ElectronReplayApp let response; try { - response = await fetch(url, {method, headers, body}); + response = await fetch(url, { method, headers, body }); } catch (e) { console.warn("fetch failed for: " + url); - callback({statusCode: 502, headers: {}, data: null}); + callback({ statusCode: 502, headers: {}, data: null }); return; } const data = method === "HEAD" ? null : response.body; const statusCode = response.status; headers = Object.fromEntries(response.headers.entries()); - callback({statusCode, headers, data}); + callback({ statusCode, headers, data }); } notFound(url, callback) { - console.log("not found: " + url); - const data = this._bufferToStream(`Sorry, the url ${url} could not be found in this archive.`); - callback({statusCode: 404, headers: {"Content-Type": "text/html; charset=\"utf-8\""}, data}); + console.log("not found: " + url); + const data = this._bufferToStream( + `Sorry, the url ${url} could not be found in this archive.`, + ); + callback({ + statusCode: 404, + headers: { "Content-Type": 'text/html; charset="utf-8"' }, + data, + }); } async resolveArchiveResponse(request, callback) { @@ -324,7 +359,13 @@ class ElectronReplayApp headers = new Headers(headers); const date = new Date(); - let response = new ArchiveResponse({payload, headers, status, date, url}); + let response = new ArchiveResponse({ + payload, + headers, + status, + date, + url, + }); const rewriter = new Rewriter({ baseUrl: url, @@ -332,7 +373,7 @@ class ElectronReplayApp urlRewrite: false, contentRewrite: true, decode: true, - useBaseRules: true + useBaseRules: true, }); request.headers = new Headers(request.headers); @@ -345,7 +386,11 @@ class ElectronReplayApp let data = await response.getBuffer(); if (status === 206 || status === 200) { - const {statusCode, start, end} = this.parseRange(request.headers, headers, data.length); + const { statusCode, start, end } = this.parseRange( + request.headers, + headers, + data.length, + ); if (start !== undefined) { data = data.slice(start, end); } @@ -353,16 +398,21 @@ class ElectronReplayApp } data = this._bufferToStream(data); - - callback({statusCode: status, headers, data}); + + callback({ statusCode: status, headers, data }); } catch (e) { console.warn(e); } - }); if (this.mainWindow) { - this.mainWindow.webContents.send("getresponse", request, this.proxyColl, this.proxyTS, channel); + this.mainWindow.webContents.send( + "getresponse", + request, + this.proxyColl, + this.proxyTS, + channel, + ); } } @@ -374,12 +424,12 @@ class ElectronReplayApp if (headers) { headers["Content-Length"] = "" + size; } - return {statusCode}; + return { statusCode }; } const m = range.match(/bytes=([\d]+)-([\d]*)/); if (!m) { - return {statusCode}; + return { statusCode }; } const start = Number(m[1]); @@ -389,7 +439,7 @@ class ElectronReplayApp headers["Content-Range"] = `bytes ${start}-${end}/${size}`; headers["Content-Length"] = `${end - start + 1}`; } - return {statusCode, start, end}; + return { statusCode, start, end }; } createMainWindow(argv) { @@ -418,7 +468,11 @@ class ElectronReplayApp getOpenUrl(argv) { 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; let sourceString = ""; @@ -438,7 +492,7 @@ class ElectronReplayApp urlParams.set("url", openUrl); } - if (openTS) { + if (openTS) { urlParams.set("ts", openTS); } @@ -451,7 +505,7 @@ class ElectronReplayApp } } -async function * readBody (body, session) { +async function* readBody(body, session) { for (const chunk of body) { if (chunk.bytes) { yield await Promise.resolve(chunk.bytes); diff --git a/src/embed-receipt.js b/src/embed-receipt.js index 97d4af7..64601a3 100644 --- a/src/embed-receipt.js +++ b/src/embed-receipt.js @@ -1,20 +1,17 @@ - import btGlobe from "../assets/globe.svg"; import btAngleDoubleDown from "../assets/chevron-double-down.svg"; import btAngleDoubleUp from "../assets/chevron-double-up.svg"; import fabGithub from "@fortawesome/fontawesome-free/svgs/brands/github.svg"; import fasDownload from "@fortawesome/fontawesome-free/svgs/solid/download.svg"; -import {clickOnSpacebarPress} from "./misc"; +import { clickOnSpacebarPress } from "./misc"; import { LitElement, html, css } from "lit"; import { tsToDate } from "./pageutils"; import prettyBytes from "pretty-bytes"; - // =========================================================================== -export class RWPEmbedReceipt extends LitElement -{ +export class RWPEmbedReceipt extends LitElement { constructor() { super(); this.collInfo = null; @@ -30,180 +27,195 @@ export class RWPEmbedReceipt extends LitElement static get properties() { return { - collInfo: {type: Object}, - appLogo: {type: Object}, - url: {type: String}, - ts: {type: String}, + collInfo: { type: Object }, + appLogo: { type: Object }, + url: { type: String }, + ts: { type: String }, - active: {type: Boolean } + active: { type: Boolean }, }; } static get embedStyles() { return css` - rwp-embed-receipt { - display: flex; - flex-direction: column; - } + rwp-embed-receipt { + display: flex; + flex-direction: column; + } - .icon { - vertical-align: text-top; - } + .icon { + vertical-align: text-top; + } - #embed-dropdown { - max-height: calc(100vh - 50px); - padding-top: 0; - margin-top: -0.5rem; - display: block; - z-index: 1; - pointer-events: none; - transition: all .3s linear; - transform-origin: left top; - transform: scaleY(0); - transition: all 300ms cubic-bezier(0.15, 0, 0.1, 1); - filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.15)); - } + #embed-dropdown { + max-height: calc(100vh - 50px); + padding-top: 0; + margin-top: -0.5rem; + display: block; + z-index: 1; + pointer-events: none; + transition: all 0.3s linear; + transform-origin: left top; + transform: scaleY(0); + transition: all 300ms cubic-bezier(0.15, 0, 0.1, 1); + filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.15)); + } - .dropdown.is-active #embed-dropdown { - transform: scaleY(1); - } + .dropdown.is-active #embed-dropdown { + transform: scaleY(1); + } - .embed-info-container { - width: 100%; - display: flex !important; - justify-content: center; - } + .embed-info-container { + width: 100%; + display: flex !important; + justify-content: center; + } - button.embed-info { - padding: 0; - background-color: white; - justify-content: space-between; - max-width: 40rem; - width: calc(100% - 1rem); - height: 42px; - border-color: #D1D5DA; - border-width: 1px; - border-style: solid; - border-radius: 999px; - display: flex; - align-items: center; - text-overflow: ellipsis; - filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.15)); - transition-duration: 50ms; - transition-timing-function: ease-out; - cursor: pointer; - z-index: 2 - } + button.embed-info { + padding: 0; + background-color: white; + justify-content: space-between; + max-width: 40rem; + width: calc(100% - 1rem); + height: 42px; + border-color: #d1d5da; + border-width: 1px; + border-style: solid; + border-radius: 999px; + display: flex; + align-items: center; + text-overflow: ellipsis; + filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.15)); + transition-duration: 50ms; + transition-timing-function: ease-out; + cursor: pointer; + z-index: 2; + } - button.embed-info:active { - color: initial; - } + button.embed-info:active { + color: initial; + } - button.embed-info:hover { - filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.2)); - transform: scale(1.01); - } + button.embed-info:hover { + filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.2)); + transform: scale(1.01); + } - button.embed-info:hover:active { - transform: translateY(0.25rem); - } + button.embed-info:hover:active { + transform: translateY(0.25rem); + } - .embed-info-buttontext { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex-grow: 1; - text-align: start; - font-size: 13px; - } + .embed-info-buttontext { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + text-align: start; + font-size: 13px; + } - .embed-info-drop { - font-size: 14px; - padding: 1rem; - padding-top: 2rem; - max-width: 38rem; - max-height: 42rem; - width: calc(100% - 2rem); - border-top-right-radius: 0px; - border-top-left-radius: 0px; - pointer-events: auto; - overflow-y: auto; - } + .embed-info-drop { + font-size: 14px; + padding: 1rem; + padding-top: 2rem; + max-width: 38rem; + max-height: 42rem; + width: calc(100% - 2rem); + border-top-right-radius: 0px; + border-top-left-radius: 0px; + pointer-events: auto; + overflow-y: auto; + } - .embed-info-drop > p { - font-size: 14px; - color: black; - } + .embed-info-drop > p { + font-size: 14px; + color: black; + } - .embed-info-drop > h2 { - margin-bottom: 0.25rem; - font-size: 16px; - font-weight: bold; - text-transform: none; - letter-spacing: 0; - color: #24292E; - } + .embed-info-drop > h2 { + margin-bottom: 0.25rem; + font-size: 16px; + font-weight: bold; + text-transform: none; + letter-spacing: 0; + color: #24292e; + } - .embed-info-drop-statscontainer > h3 { - font-size: 12px; - color: #394146; - } + .embed-info-drop-statscontainer > h3 { + font-size: 12px; + color: #394146; + } - .embed-info-drop-statscontainer > p { - font-size: 14px; - color: black; - } + .embed-info-drop-statscontainer > p { + font-size: 14px; + color: black; + } - .embed-info-drop a { - word-break: break-all; - } + .embed-info-drop a { + word-break: break-all; + } - .embed-info-drop .show-hash { - word-break: break-all; - font-family: monospace; - } + .embed-info-drop .show-hash { + word-break: break-all; + font-family: monospace; + } - .embed-info-drop .show-key { - text-overflow: ellipsis; - overflow: hidden; - whitespace: nowrap; - font-family: monospace; - } + .embed-info-drop .show-key { + text-overflow: ellipsis; + overflow: hidden; + whitespace: nowrap; + font-family: monospace; + } - .embed-globe { - margin: 0.25rem; - padding: 7px; - background-color: #0366D6; - border-radius: 9999px; - color: white; - border-width: 1px; - border-color: #D1D5DA; - border-style: solid; - line-height: 0.5em; - }`; + .embed-globe { + margin: 0.25rem; + padding: 7px; + background-color: #0366d6; + border-radius: 9999px; + color: white; + border-width: 1px; + border-color: #d1d5da; + border-style: solid; + line-height: 0.5em; + } + `; } render() { - let {numValid, numInvalid, domain, certFingerprint, datapackageHash, publicKey, software} = this.collInfo.verify || {}; + let { + numValid, + numInvalid, + domain, + certFingerprint, + datapackageHash, + publicKey, + software, + } = this.collInfo.verify || {}; numValid = numValid || 0; numInvalid = numInvalid || 0; const sourceUrl = this.collInfo.sourceUrl; - const certFingerprintUrl = certFingerprint ? `https://crt.sh/?q=${certFingerprint}` : ""; + const certFingerprintUrl = certFingerprint + ? `https://crt.sh/?q=${certFingerprint}` + : ""; const dateStr = tsToDate(this.ts).toLocaleString(); return html` - ${sourceUrl ? html` - ` : ""} + ${sourceUrl ? html`` : ""}

- + Powered by ReplayWeb.page diff --git a/src/embed.js b/src/embed.js index d2df617..e014835 100644 --- a/src/embed.js +++ b/src/embed.js @@ -4,17 +4,14 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { wrapCss, rwpLogo } from "./misc"; import { SWManager } from "./swmanager"; - var scriptSrc = document.currentScript && document.currentScript.src; var defaultReplayFile = ""; const DEFAULT_REPLAY_BASE = "https://replayweb.page/"; - // =========================================================================== -class Embed extends LitElement -{ +class Embed extends LitElement { constructor() { super(); this.replaybase = "./replay/"; @@ -86,7 +83,7 @@ class Embed extends LitElement errorMessage: { type: String }, - requireSubdomainIframe: {type: Boolean}, + requireSubdomainIframe: { type: Boolean }, loading: { type: String }, @@ -109,7 +106,12 @@ class Embed extends LitElement const scope = this.replaybase; const requireSubdomainIframe = this.requireSubdomainIframe; - this.swmanager = new SWManager({name, scope, requireSubdomainIframe, appName}); + this.swmanager = new SWManager({ + name, + scope, + requireSubdomainIframe, + appName, + }); try { await this.swmanager.register(); @@ -144,7 +146,9 @@ class Embed extends LitElement firstUpdated() { if (this.noSandbox) { - console.warn("The noSandbox flag is deprecated. ReplayWeb.page does not add a sandbox by default. To enable sandboxing, use 'sandbox' flag instead. This may result in PDFs not loading and pages opening in new windows, but may be more secure in some situations"); + console.warn( + "The noSandbox flag is deprecated. ReplayWeb.page does not add a sandbox by default. To enable sandboxing, use 'sandbox' flag instead. This may result in PDFs not loading and pages opening in new windows, but may be more secure in some situations", + ); } this.doRegister(); @@ -163,7 +167,10 @@ class Embed extends LitElement // https://lil.law.harvard.edu/blog/2022/09/15/opportunities-and-challenges-of-client-side-playback/ // likely safari < 16, don't use web workers due to issues with split storage state - if (window.GestureEvent !== undefined && window.SharedWorker === undefined ){ + if ( + window.GestureEvent !== undefined && + window.SharedWorker === undefined + ) { this.noWebWorker = true; } @@ -196,7 +203,7 @@ class Embed extends LitElement } if (this.config) { - const config = {...this.customConfig, ...JSON.parse(this.config)}; + const config = { ...this.customConfig, ...JSON.parse(this.config) }; return JSON.stringify(config); } else { return JSON.stringify(this.customConfig); @@ -204,13 +211,14 @@ class Embed extends LitElement } updated(changedProperties) { - if (changedProperties.has("url") || - changedProperties.has("ts") || - changedProperties.has("query") || - changedProperties.has("view") || - changedProperties.has("source") || - changedProperties.has("src")) { - + if ( + changedProperties.has("url") || + changedProperties.has("ts") || + changedProperties.has("query") || + changedProperties.has("view") || + changedProperties.has("source") || + changedProperties.has("src") + ) { this.embed = this.embed || "default"; if (this.src) { @@ -265,7 +273,7 @@ class Embed extends LitElement url: this.url, ts: this.ts, query: this.query, - view: this.view + view: this.view, }).toString(); } } @@ -300,18 +308,26 @@ class Embed extends LitElement render() { return html` - ${this.paramString && this.hashString && this.inited ? html` - - - ` : html``} - - ${this.errorMessage} + allow-same-origin allow-forms" + : undefined, + )}" + @load="${this.onLoad}" + src="${this.replaybase}${this.replayfile}?${this + .paramString}#${this.hashString}" + allow="autoplay *; fullscreen" + title="Replay of ${this.title ? `${this.title}:` : ""} ${this + .url}" + > + ` + : html``} + ${this.errorMessage} `; } @@ -323,7 +339,11 @@ class Embed extends LitElement const win = event.target.contentWindow; const doc = event.target.contentDocument; - if (win.navigator.serviceWorker && !win.navigator.serviceWorker.controller && this.reloadCount <= 2) { + if ( + win.navigator.serviceWorker && + !win.navigator.serviceWorker.controller && + this.reloadCount <= 2 + ) { this.reloadCount++; setTimeout(() => win.location.reload(), 100); return; diff --git a/src/gdrive.js b/src/gdrive.js index 92d9934..655f31b 100644 --- a/src/gdrive.js +++ b/src/gdrive.js @@ -3,10 +3,8 @@ import { wrapCss } from "./misc"; import fabGoogleDrive from "@fortawesome/fontawesome-free/svgs/brands/google-drive.svg"; - // =========================================================================== -class GDrive extends LitElement -{ +class GDrive extends LitElement { constructor() { super(); this.state = "trypublic"; @@ -20,7 +18,7 @@ class GDrive extends LitElement state: { type: String }, sourceUrl: { type: String }, error: { type: Boolean }, - reauth: { type: Boolean } + reauth: { type: Boolean }, }; } @@ -65,22 +63,25 @@ class GDrive extends LitElement try { const abort = new AbortController(); const signal = abort.signal; - resp = await fetch(publicUrl, {signal}); + resp = await fetch(publicUrl, { signal }); abort.abort(); if (resp.status != 200) { return false; } - } catch(e) { + } catch (e) { return false; } const name = json.name; - const extra = {publicUrl}; + const extra = { publicUrl }; const size = Number(json.size); - - this.dispatchEvent(new CustomEvent("load-ready", {detail: {name, extra, size, sourceUrl}})); - return true; + this.dispatchEvent( + new CustomEvent("load-ready", { + detail: { name, extra, size, sourceUrl }, + }), + ); + return true; } catch (e) { return false; } @@ -112,11 +113,14 @@ class GDrive extends LitElement const fileId = sourceUrl.slice("googledrive://".length); const metadataUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`; const authToken = response.access_token; - const headers = {"Authorization": `Bearer ${authToken}`}; + const headers = { Authorization: `Bearer ${authToken}` }; - const resp = await fetch(metadataUrl + "?fields=name,size&supportsAllDrives=true", {headers}); + const resp = await fetch( + metadataUrl + "?fields=name,size&supportsAllDrives=true", + { headers }, + ); - if ((resp.status === 404 || resp.status == 403)) { + if (resp.status === 404 || resp.status == 403) { if (this.state !== "implicitonly") { this.state = "trymanual"; } @@ -130,7 +134,11 @@ class GDrive extends LitElement const name = metadata.name; const size = Number(metadata.size); - this.dispatchEvent(new CustomEvent("load-ready", {detail: {sourceUrl, headers, size, name}})); + this.dispatchEvent( + new CustomEvent("load-ready", { + detail: { sourceUrl, headers, size, name }, + }), + ); } static get styles() { @@ -138,24 +146,36 @@ class GDrive extends LitElement } render() { - return html` - ${this.script()} - ${this.state !== "trymanual" ? html` -

Connecting to Google Drive...

- ` : html` - ${this.error ? html` -
-

${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."}

-

If you have multiple Google Drive accounts, be sure to select the correct one.

-
-
- ` : ""} - - `}`; + return html` ${this.script()} + ${this.state !== "trymanual" + ? html`

Connecting to Google Drive...

` + : html` + ${this.error + ? html` +
+

+ ${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."} +

+

+ If you have multiple Google Drive accounts, be sure to + select the correct one. +

+
+
+ ` + : ""} + + `}`; } script() { @@ -163,20 +183,23 @@ class GDrive extends LitElement return html``; } const script = document.createElement("script"); - script.onload = (() => this.onLoad()); + script.onload = () => this.onLoad(); script.src = "https://apis.google.com/js/platform.js"; return script; } gauth(prompt, callback) { self.gapi.load("auth2", () => { - self.gapi.auth2.authorize({ - // eslint-disable-next-line no-undef - client_id: __GDRIVE_CLIENT_ID__, - scope: "https://www.googleapis.com/auth/drive.file", - response_type: "token", - prompt - }, callback); + self.gapi.auth2.authorize( + { + // eslint-disable-next-line no-undef + client_id: __GDRIVE_CLIENT_ID__, + scope: "https://www.googleapis.com/auth/drive.file", + response_type: "token", + prompt, + }, + callback, + ); }); } } diff --git a/src/loader.js b/src/loader.js index eca7886..9733400 100644 --- a/src/loader.js +++ b/src/loader.js @@ -5,10 +5,8 @@ import prettyBytes from "pretty-bytes"; import { parseURLSchemeHostPath } from "./pageutils"; - // =========================================================================== -class Loader extends LitElement -{ +class Loader extends LitElement { constructor() { super(); this.progress = 0; @@ -41,7 +39,7 @@ class Loader extends LitElement percent: { type: Number }, currentSize: { type: Number }, totalSize: { type: Number }, - error: { type: String}, + error: { type: String }, total: { type: Number }, status: { type: String }, coll: { type: String }, @@ -74,48 +72,53 @@ class Loader extends LitElement this.worker.addEventListener("message", (event) => { switch (event.data.msg_type) { - case "collProgress": - if (event.data.name === this.coll) { - this.percent = event.data.percent; - if (event.data.error) { - this.error = event.data.error; - this.state = "errored"; - this.errorAllowRetry = true; - this.fileHandle = event.data.fileHandle; - if (this.error === "missing_local_file") { - this.tryFileHandle = false; - } else if (this.error === "permission_needed" && event.data.fileHandle) { - this.state = "permission_needed"; - break; + case "collProgress": + if (event.data.name === this.coll) { + this.percent = event.data.percent; + if (event.data.error) { + this.error = event.data.error; + this.state = "errored"; + this.errorAllowRetry = true; + this.fileHandle = event.data.fileHandle; + if (this.error === "missing_local_file") { + this.tryFileHandle = false; + } else if ( + this.error === "permission_needed" && + event.data.fileHandle + ) { + this.state = "permission_needed"; + break; + } } - } - if (event.data.currentSize && event.data.totalSize) { - this.currentSize = event.data.currentSize; - this.totalSize = event.data.totalSize; - } - this.extraMsg = event.data.extraMsg; - } - break; - - case "collAdded": - if (event.data.name === this.coll) { - if (!this.total) { - this.total = 100; - } - this.progress = this.total; - this.percent = 100; - this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data})); - - if (!this.noWebWorker) { - this.worker.terminate(); - } else { - if (this.pingInterval) { - clearInterval(this.pingInterval); + if (event.data.currentSize && event.data.totalSize) { + this.currentSize = event.data.currentSize; + this.totalSize = event.data.totalSize; } + this.extraMsg = event.data.extraMsg; } - this.worker = null; - } - break; + break; + + case "collAdded": + if (event.data.name === this.coll) { + if (!this.total) { + this.total = 100; + } + this.progress = this.total; + this.percent = 100; + this.dispatchEvent( + new CustomEvent("coll-loaded", { detail: event.data }), + ); + + if (!this.noWebWorker) { + this.worker.terminate(); + } else { + if (this.pingInterval) { + clearInterval(this.pingInterval); + } + } + this.worker = null; + } + break; } }); } @@ -137,43 +140,45 @@ class Loader extends LitElement // custom protocol handlers here... try { - const {scheme, host, path} = parseURLSchemeHostPath(sourceUrl); + const { scheme, host, path } = parseURLSchemeHostPath(sourceUrl); switch (scheme) { - case "googledrive": - this.state = "googledrive"; - source = await this.googledriveInit(); - break; + case "googledrive": + this.state = "googledrive"; + source = await this.googledriveInit(); + break; - case "s3": - source = {sourceUrl, - loadUrl: `https://${host}.s3.amazonaws.com${path}`, - name: this.sourceUrl}; - break; + case "s3": + source = { + sourceUrl, + loadUrl: `https://${host}.s3.amazonaws.com${path}`, + name: this.sourceUrl, + }; + break; - case "file": - if (!this.loadInfo && !this.tryFileHandle) { - this.state = "errored"; - this.error = `\ + case "file": + if (!this.loadInfo && !this.tryFileHandle) { + this.state = "errored"; + this.error = `\ File URLs can not be entered directly or shared. You can select a file to upload from the main page by clicking the 'Choose File...' button.`; - this.errorAllowRetry = false; - return; - } + this.errorAllowRetry = false; + return; + } - source = this.loadInfo; - break; + source = this.loadInfo; + break; - case "proxy": - sourceUrl = "proxy:" + sourceUrl.slice("proxy://".length); - break; + case "proxy": + sourceUrl = "proxy:" + sourceUrl.slice("proxy://".length); + break; } } catch (e) { console.log(e); } if (!source) { - source = {sourceUrl}; + source = { sourceUrl }; } this.state = "started"; @@ -190,7 +195,11 @@ You can select a file to upload from the main page by clicking the 'Choose File. extraConfig = this.loadInfo.extraConfig; } // todo: too special case? - if (sourceUrl.startsWith("proxy:") && extraConfig && extraConfig.recording) { + if ( + sourceUrl.startsWith("proxy:") && + extraConfig && + extraConfig.recording + ) { type = "recordingproxy"; } } @@ -201,12 +210,14 @@ You can select a file to upload from the main page by clicking the 'Choose File. extraConfig, type, skipExisting: true, - file: source + file: source, }; if (!navigator.serviceWorker.controller) { await new Promise((resolve) => { - navigator.serviceWorker.addEventListener("controllerchange", () => resolve()); + navigator.serviceWorker.addEventListener("controllerchange", () => + resolve(), + ); }); } @@ -219,14 +230,14 @@ You can select a file to upload from the main page by clicking the 'Choose File. // ping service worker with messages to avoid shutdown while loading // (mostly for Firefox) this.pingInterval = setInterval(() => { - navigator.serviceWorker.controller.postMessage({"msg_type": "ping"}); + navigator.serviceWorker.controller.postMessage({ msg_type: "ping" }); }, 15000); } } } googledriveInit() { - this._gdWait = new Promise((resolve) => this._gdResolve = resolve); + this._gdWait = new Promise((resolve) => (this._gdResolve = resolve)); return this._gdWait; } @@ -244,7 +255,7 @@ You can select a file to upload from the main page by clicking the 'Choose File. return; } - const msg = {"msg_type": "cancelLoad", "name": this.coll}; + const msg = { msg_type: "cancelLoad", name: this.coll }; if (!this.noWebWorker) { this.worker.postMessage(msg); @@ -260,7 +271,10 @@ You can select a file to upload from the main page by clicking the 'Choose File. } updated(changedProperties) { - if (this.sourceUrl && changedProperties.has("sourceUrl") || changedProperties.has("tryFileHandle")) { + if ( + (this.sourceUrl && changedProperties.has("sourceUrl")) || + changedProperties.has("tryFileHandle") + ) { this.doLoad(); } } @@ -314,73 +328,96 @@ You can select a file to upload from the main page by clicking the 'Choose File. render() { return html` -
-
-
- ${!this.embed ? html` -
-

Loading ${this.sourceUrl}...

-
` : ""} -
-
- ${this.renderContent()} +
+
+
-
-
+ ${!this.embed + ? html`
+

Loading ${this.sourceUrl}...

+
` + : ""} +
+
+ ${this.renderContent()} +
+
+
`; } renderContent() { switch (this.state) { - case "googledrive": - return html``; + case "googledrive": + return html``; - case "started": - return html` -
- - - ${this.currentSize && this.totalSize ? html` -
Loaded ${prettyBytes(this.currentSize)} of ${prettyBytes(this.totalSize)} - ${this.extraMsg && html` -

(${this.extraMsg})

- `} -
` : html``} + case "started": + return html`
+ + + ${this.currentSize && this.totalSize + ? html`
+ Loaded ${prettyBytes(this.currentSize)} of + ${prettyBytes(this.totalSize)} + ${this.extraMsg && + html`

(${this.extraMsg})

`} +
` + : html``} + ${!this.embed + ? html` ` + : ""} +
`; - ${!this.embed ? html` - ` : ""} -
`; - - case "errored": - return html` -
+ case "errored": + return html`
${this.error}
- ${this.errorAllowRetry ? html` - window.parent.location.reload()}>Try Again` : ""} - ${this.embed ? html`` : html` - Back`} -
`; + ${this.errorAllowRetry + ? html` window.parent.location.reload()} + >Try Again` + : ""} + ${this.embed + ? html`` + : html` Back`} +
+
`; - case "permission_needed": - return html` -
-
Permission is needed to reload the archive file. (Click Cancel to cancel loading this archive.)
- + case "permission_needed": + return html`
+
+ Permission is needed to reload the archive file. (Click + Cancel to cancel loading this archive.) +
+ Cancel
`; - case "waiting": - default: - return html``; - + case "waiting": + default: + return html``; } } async onAskPermission() { - const result = await this.fileHandle.requestPermission({mode: "read"}); + const result = await this.fileHandle.requestPermission({ mode: "read" }); if (result === "granted") { this.doLoad(); } diff --git a/src/misc.js b/src/misc.js index 186682a..edbe16a 100644 --- a/src/misc.js +++ b/src/misc.js @@ -11,14 +11,16 @@ import rwpLogo from "../assets/logo.svg"; const apiPrefix = "./w/api"; const replayPrefix = "./w"; - // =========================================================================== const allCss = unsafeCSS(allCssRaw); function wrapCss(custom) { return [allCss, custom]; } -const IS_APP = window.IS_APP || window.electron && window.electron.IS_APP || window.matchMedia("(display-mode: standalone)").matches; +const IS_APP = + window.IS_APP || + (window.electron && window.electron.IS_APP) || + window.matchMedia("(display-mode: standalone)").matches; // eslint-disable-next-line no-undef const VERSION = __VERSION__; @@ -33,10 +35,8 @@ function clickOnSpacebarPress(event) { } } - // =========================================================================== -class FaIcon extends LitElement -{ +class FaIcon extends LitElement { constructor() { super(); this.size = "1.1em"; @@ -49,23 +49,23 @@ class FaIcon extends LitElement svg: { type: String }, size: { type: String }, width: { type: String }, - height: { type: String } + height: { type: String }, }; } static get styles() { return css` - :host { - display: inline-block; - padding: 0; - margin: 0; - line-height: 1.0em; - } - :host svg { - fill: var(--fa-icon-fill-color, currentcolor); - width: var(--fa-icon-width, 19px); - height: var(--fa-icon-height, 19px); - } + :host { + display: inline-block; + padding: 0; + margin: 0; + line-height: 1em; + } + :host svg { + fill: var(--fa-icon-fill-color, currentcolor); + width: var(--fa-icon-width, 19px); + height: var(--fa-icon-height, 19px); + } `; } @@ -88,14 +88,14 @@ class FaIcon extends LitElement } } - return html`${unsafeSVG(this.svg)}`; + return html` + ${unsafeSVG(this.svg)} + `; } } - // =========================================================================== -class AnimLogo extends FaIcon -{ +class AnimLogo extends FaIcon { constructor() { super(); this.svg = rwpLogo; @@ -103,37 +103,55 @@ class AnimLogo extends FaIcon static get styles() { return css` - #wrlogo #stop5687 { - animation: animLeft 7s linear infinite; - } + #wrlogo #stop5687 { + animation: animLeft 7s linear infinite; + } - #wrlogo #stop5689 { - animation: animRight 7s linear infinite; - } + #wrlogo #stop5689 { + animation: animRight 7s linear infinite; + } - @keyframes animLeft { - 0% {stop-color: #4876ff} - 25% {stop-color: #1b0921} - 50% {stop-color: #4876ff} - 75% {stop-color: #04cdff} - 100% {stop-color: #4876ff} - } + @keyframes animLeft { + 0% { + stop-color: #4876ff; + } + 25% { + stop-color: #1b0921; + } + 50% { + stop-color: #4876ff; + } + 75% { + stop-color: #04cdff; + } + 100% { + stop-color: #4876ff; + } + } - @keyframes animRight { - 0% {stop-color: #04cdff} - 25% {stop-color: #4876ff} - 50% {stop-color: #1b0921} - 75% {stop-color: #4876ff} - 100% {stop-color: #04cdff} - } + @keyframes animRight { + 0% { + stop-color: #04cdff; + } + 25% { + stop-color: #4876ff; + } + 50% { + stop-color: #1b0921; + } + 75% { + stop-color: #4876ff; + } + 100% { + stop-color: #04cdff; + } + } `; } } - // =========================================================================== -class WrModal extends LitElement -{ +class WrModal extends LitElement { constructor() { super(); this.title = ""; @@ -145,35 +163,41 @@ class WrModal extends LitElement return { title: { type: String }, bgClass: { type: String }, - noBgClose: { type: Boolean } + noBgClose: { type: Boolean }, }; } static get styles() { return wrapCss(css` - .modal-background { - background-color: rgba(10, 10, 10, 0.50); - } + .modal-background { + background-color: rgba(10, 10, 10, 0.5); + } - .modal-card-head { - background-color: var(--background, #97a1ff); - } + .modal-card-head { + background-color: var(--background, #97a1ff); + } - .modal-card { - width: 100%; - max-width: var(--modal-width, 640px) - } + .modal-card { + width: 100%; + max-width: var(--modal-width, 640px); + } `); } render() { - return html` -