Update NPM packaging, linting, and a bunch of types (#275)

Preparation for
https://github.com/webrecorder/archiveweb.page/issues/198 /
https://github.com/webrecorder/archiveweb.page/pull/199.

## Changes

- Organizes published files more cleanly, using `dist/types` folder as
the target for type declarations and sourcemaps
- Configures type field in `package.json`
- Updates ESLint & its config
  - Sets up type-informed linting rules mirroring browsertrix
- Adds a bunch of other useful eslint and eslint-typescript rules, and
applies auto-fixes to the codebase
- Cleans up a bunch of types throughout the codebase, particularly parts
of the code used by ArchiveWebPage
- Changes `ItemIndex#_deleting` from `private` to `protected` so that a
part of AWP that extends it can do so properly
- Makes a bunch of previously-implied [non-null
assertions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix-)
explicit
- Updates `URLResource` and `ItemType` types (and renames `Item` →
`ItemType` to disambiguate from the `Item` class)
- Removes `node-sass` and replaces it with `sass-embedded` for much
quicker installs and compilation
This commit is contained in:
Emma Segal-Grossman
2024-02-06 18:50:12 -08:00
committed by GitHub
parent a40471128f
commit a63390f10c
32 changed files with 898 additions and 1550 deletions
+92 -14
View File
@@ -1,32 +1,110 @@
/* eslint-env node */
/** @type { import("eslint").Linter.Config } */
/** @type {import('eslint').Linter.Config} */
module.exports = {
parser: "@typescript-eslint/parser",
env: {
browser: true,
es6: true,
commonjs: true,
es2017: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:wc/recommended",
"plugin:lit/recommended",
"prettier",
"plugin:@typescript-eslint/recommended",
],
plugins: ["@typescript-eslint", "lit"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
project: ["./tsconfig.eslint.json"],
tsconfigRootDir: __dirname,
},
root: true,
rules: {
/* start stylistic rules */
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/prefer-readonly": "warn",
"@typescript-eslint/class-literal-property-style": ["warn", "getters"],
"@typescript-eslint/consistent-generic-constructors": "error",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
"@typescript-eslint/no-inferrable-types": "warn",
"@typescript-eslint/non-nullable-type-assertion-style": "warn",
"@typescript-eslint/prefer-for-of": "warn",
// "@typescript-eslint/prefer-nullish-coalescing": "warn",
"@typescript-eslint/prefer-optional-chain": "warn",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
/* end stylistic rules */
/* start recommended rules */
"no-restricted-globals": [2, "event", "error"],
"linebreak-style": ["error", "unix"],
"@typescript-eslint/no-base-to-string": "warn",
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-duplicate-type-constituents": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-extra-non-null-assertion": "error",
// "@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/no-for-in-array": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
},
],
"no-implied-eval": "off",
"@typescript-eslint/no-implied-eval": "error",
"no-loss-of-precision": "off",
"@typescript-eslint/no-loss-of-precision": "warn",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-misused-promises": [
"error",
{ checksVoidReturn: { arguments: false } },
],
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "warn",
"@typescript-eslint/no-redundant-type-constituents": "warn",
"@typescript-eslint/no-this-alias": "warn",
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
"@typescript-eslint/no-unnecessary-type-constraint": "warn",
// "@typescript-eslint/no-unsafe-argument": "warn",
// "@typescript-eslint/no-unsafe-assignment": "warn",
// "@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-declaration-merging": "warn",
"@typescript-eslint/no-unsafe-enum-comparison": "warn",
// "@typescript-eslint/no-unsafe-member-access": "warn",
// "@typescript-eslint/no-unsafe-return": "warn",
"@typescript-eslint/prefer-as-const": "warn",
"require-await": "off",
"@typescript-eslint/require-await": "warn",
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/unbound-method": "off",
},
ignorePatterns: [
"ruffle/**/*",
"build/**/*",
"/sw.js",
"/ui.js",
"dist/**/*",
],
reportUnusedDisableDirectives: true,
ignorePatterns: [
"__generated__",
"__mocks__",
"dist",
"ruffle",
"ui.js",
"sw.js",
],
overrides: [
{
extends: ["plugin:@typescript-eslint/disable-type-checked"],
files: ["webpack.*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off",
},
},
],
};
+1
View File
@@ -1,3 +1,4 @@
/*eslint-env node */
const { notarize } = require('electron-notarize');
exports.default = async function notarizing(context) {
-1
View File
@@ -1 +0,0 @@
export * from "./ui.js";
+11 -9
View File
@@ -7,6 +7,8 @@
"homepage": "https://replayweb.page/",
"author": "Webrecorder Software",
"license": "AGPL-3.0-or-later",
"main": "ui.js",
"types": "dist/types/index.d.ts",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@shoelace-style/shoelace": "^2.8.0",
@@ -29,26 +31,27 @@
"stream-browserify": "^3.0.0"
},
"devDependencies": {
"@types/flexsearch": "^0.7.3",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@playwright/test": "^1.38.1",
"@types/flexsearch": "^0.7.3",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.2.0",
"electron": "^25.1.1",
"electron-builder": "^23.0.3",
"electron-notarize": "^1.1.1",
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-lit": "^1.9.1",
"eslint": "^8.54.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-lit": "^1.10.1",
"eslint-plugin-wc": "^1.3.2",
"http-server": "^13.0.2",
"husky": "^8.0.0",
"lint-staged": "^14.0.0",
"mini-css-extract-plugin": "^2.3.0",
"node-sass": "^9.0.0",
"playwright": "^1.38.1",
"prettier": "^3.0.1",
"raw-loader": "^4.0.2",
"sass-embedded": "^1.70.0",
"sass-loader": "^13.3.2",
"style-loader": "^3.2.1",
"svg-inline-loader": "^0.8.2",
@@ -62,11 +65,11 @@
},
"files": [
"/assets",
"/dist",
"/src",
"/ruffle",
"sw.js",
"ui.js",
"index.js",
"index.html"
],
"scripts": {
@@ -102,7 +105,6 @@
"dist/*.js",
"dist/prebuilds/${platform}-${arch}/*",
"dist/build/*",
"index.js",
"ui.js",
"sw.js",
"*.html",
+1 -2
View File
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-env node */
// Runs a webpack build when not installed in a git repository (i.e. when installed from GitHub in node_modules)
@@ -26,7 +25,7 @@ const git = (args) => cp.spawnSync("git", args, { stdio: "inherit" });
// Ensure that we're inside a Git repository
// If git command is not found, status is null and we should return
// That's why status value needs to be checked explicitly
if (git(["rev-parse"]).status !== 0) {
if (git(["rev-parse"]).status !== 0 || process.argv.includes("--force")) {
l(`git command not found, running build`);
const compiler = webpack(config.map((c) => c()));
+11 -14
View File
@@ -2,6 +2,7 @@ import {
LitElement,
html,
css,
type CSSResultGroup,
type TemplateResult,
type PropertyValues,
} from "lit";
@@ -22,7 +23,7 @@ import { SWManager } from "./swmanager";
import "./item";
import "./item-index";
import "./chooser";
import { LoadInfo } from "./item";
import { type LoadInfo } from "./item";
// ===========================================================================
@customElement("replay-app-main")
@@ -69,10 +70,10 @@ export class ReplayWebApp extends LitElement {
@property({ type: Boolean })
skipRuffle = false;
@property({ type: Object })
swErrorMsg: TemplateResult<1> | "" | null = null;
@property({ type: String })
swErrorMsg: TemplateResult<1> | string | null = null;
private swName?: string;
protected swName?: string;
private swmanager: SWManager | null;
private useRuffle = false;
@@ -112,7 +113,7 @@ export class ReplayWebApp extends LitElement {
});
}
private maybeStartFileDrop = (dragEvent: DragEvent) => {
private readonly maybeStartFileDrop = (dragEvent: DragEvent) => {
if (this.sourceUrl) {
// A source is already loaded. Don't allow dropping a file.
return;
@@ -133,7 +134,7 @@ export class ReplayWebApp extends LitElement {
return wrapCss(ReplayWebApp.appStyles);
}
static get appStyles() {
static get appStyles(): CSSResultGroup {
return css`
#wrlogo {
max-height: 1rem;
@@ -237,7 +238,7 @@ export class ReplayWebApp extends LitElement {
}
get mainLogo() {
return rwpLogo;
return rwpLogo as string;
}
renderNavBrand() {
@@ -499,7 +500,7 @@ export class ReplayWebApp extends LitElement {
// This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors.
event.preventDefault();
(this.renderRoot.querySelector("#skip-main-target") as HTMLElement).focus();
this.renderRoot.querySelector<HTMLElement>("#skip-main-target")?.focus();
}
onNavMenu(event) {
@@ -516,9 +517,7 @@ export class ReplayWebApp extends LitElement {
(event) => {
event.preventDefault();
this.navMenuShown = false;
(
this.renderRoot.querySelector("#menu-button") as HTMLElement
).focus();
this.renderRoot.querySelector<HTMLElement>("#menu-button")?.focus();
},
{ once: true },
);
@@ -528,9 +527,7 @@ export class ReplayWebApp extends LitElement {
if (event.key == "Escape") {
event.preventDefault();
this.navMenuShown = false;
(
this.renderRoot.querySelector("#menu-button") as HTMLElement
).focus();
this.renderRoot.querySelector<HTMLElement>("#menu-button")?.focus();
}
},
{ once: true },
+10 -22
View File
@@ -1,8 +1,8 @@
/*eslint-env node */
import { CollectionLoader } from "@webrecorder/wabac/src/loaders";
import { type IpcRendererEvent } from "electron";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ipcRenderer, contextBridge } = require("electron");
contextBridge.exposeInMainWorld("electron", { IS_APP: true });
@@ -11,12 +11,7 @@ const dbs = {};
const loader = new CollectionLoader();
/**
*
* @param {string} name
* @returns
*/
async function getColl(name) {
async function getColl(name: string) {
if (!dbs[name]) {
dbs[name] = await loader.loadColl(name);
await dbs[name].initing;
@@ -25,25 +20,18 @@ async function getColl(name) {
return dbs[name];
}
/**
* @param {string} name
* @returns
*/
async function getDB(name) {
async function getDB(name: string) {
const coll = await getColl(name);
return coll.store;
}
/**
*
* @param {import("electron").IpcRendererEvent} event
* @param {*} request
* @param {*} coll
* @param {*} ts
* @param {*} channel
* @returns
*/
async function getResponse(event, request, coll, ts, channel) {
async function getResponse(
event: IpcRendererEvent,
request: Request,
coll: string,
ts: string,
channel: string,
): Promise<void> {
const db = await getDB(coll);
const req = { request, url: request.url, timestamp: ts };
+9 -3
View File
@@ -250,7 +250,14 @@ class ElectronReplayApp {
return rv;
}
async doIntercept(request, callback) {
async doIntercept(
request,
callback: (response: {
statusCode: number;
headers: Record<string, string>;
data: fs.ReadStream;
}) => void,
) {
console.log(`${request.method} ${request.url} from ${request.referrer}`);
// if local server
@@ -303,7 +310,7 @@ class ElectronReplayApp {
}
// possible 'live leak' attempt, return archived version, if any
if (request.referrer && request.referrer.startsWith(REPLAY_PREFIX)) {
if (request.referrer?.startsWith(REPLAY_PREFIX)) {
return await this.resolveArchiveResponse(request, callback);
}
@@ -483,7 +490,6 @@ class ElectronReplayApp {
}
getOpenUrl(argv) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
argv = require("minimist")(argv.slice(process.defaultApp ? 2 : 1));
const filename =
+2 -2
View File
@@ -10,7 +10,7 @@ import { LitElement, html, css, nothing } from "lit";
import { tsToDate } from "./pageutils";
import prettyBytes from "pretty-bytes";
import { property } from "lit/decorators.js";
import type { Item as ItemType } from "./types";
import type { ItemType } from "./types";
// ===========================================================================
export class RWPEmbedReceipt extends LitElement {
@@ -183,7 +183,7 @@ export class RWPEmbedReceipt extends LitElement {
datapackageHash,
publicKey,
software,
} = this.collInfo?.verify || {};
} = this.collInfo?.verify ?? {};
const sourceUrl = this.collInfo?.sourceUrl;
+2 -3
View File
@@ -2,7 +2,7 @@ import {
LitElement,
html,
css,
TemplateResult,
type TemplateResult,
type PropertyValues,
} from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
@@ -53,7 +53,6 @@ class Embed extends LitElement {
@property({ type: String }) newWindowBase = "";
@property({ type: String }) errorMessage:
| ""
| TemplateResult<1>
| string
| undefined;
@@ -163,7 +162,7 @@ class Embed extends LitElement {
}
// if no storage manager or estimate, don't cache
if (!navigator.storage || !navigator.storage.estimate) {
if (!navigator.storage?.estimate) {
this.noCache = true;
}
}
+46
View File
@@ -0,0 +1,46 @@
declare module "*.svg";
declare module "*.html";
declare module "*.scss";
declare module "*.sass";
declare const __SW_NAME__: string;
declare const __HELPER_PROXY__: string;
declare const __GDRIVE_CLIENT_ID__: string;
declare const __VERSION__: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TODOFixMe = any;
// showOpenFilePicker doesn't yet exist in Typescript's DOM lib
// see https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker for details
interface Window {
showOpenFilePicker: (options?: {
types: { description: string; accept: Record<string, string[]> }[];
}) => Promise<[FileSystemFileHandle]>;
}
// This is only supported in Blink (see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle/requestPermission#browser_compatibility), so Typescript leaves it out of their definitions (https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1393#issuecomment-1483735315)
interface FileSystemHandle {
requestPermission: (options?: {
mode?: "read" | "readwrite";
}) => Promise<PermissionStatus>;
}
type GAPIResponse =
| { error: string; access_token: never }
| { error: never; access_token: string };
interface Window {
gapi: {
load: (method: string, callback: (response: GAPIResponse) => void) => void;
auth2: {
authorize: (
config: {
client_id: string;
scope: string;
response_type: string;
prompt: unknown;
},
callback: (response: GAPIResponse) => void,
) => void;
};
};
}
-21
View File
@@ -1,21 +0,0 @@
declare module "*.svg";
declare module "*.html";
declare const __SW_NAME__: string;
declare const __HELPER_PROXY__: string;
declare const __GDRIVE_CLIENT_ID__: string;
declare const __VERSION__: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TODOFixMe = any;
// showOpenFilePicker doesn't yet exist in Typescript's DOM lib
// see https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker for details
interface Window {
showOpenFilePicker: (options?: {
types: { description: string; accept: Record<string, string[]> }[];
}) => Promise<[FileSystemFileHandle]>;
}
interface Window {
gapi: TODOFixMe;
}
+17 -3
View File
@@ -17,9 +17,21 @@ import "./shoelace";
export {
ReplayWebApp,
Chooser,
ItemIndex as CollIndex, // @todo(2023-11-06) complete rename
ItemInfo as CollInfo, // @todo(2023-11-06) complete rename
Item as Coll, // @todo(2023-11-06) complete rename
ItemIndex,
ItemInfo,
Item,
/**
* @deprecated use {@linkcode ItemIndex}
*/
ItemIndex as CollIndex,
/**
* @deprecated use {@linkcode ItemInfo}
*/
ItemInfo as CollInfo,
/**
* @deprecated use {@linkcode Item}
*/
Item as Coll,
Story,
GDrive,
Loader,
@@ -30,3 +42,5 @@ export {
URLResources,
Embed,
};
export type { ItemType, URLResource } from "./types";
+28 -17
View File
@@ -7,26 +7,26 @@ import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import type { Item } from "./types";
import type { ItemType } from "./types";
import "./item-info";
// ===========================================================================
class ItemIndex extends LitElement {
@property({ type: Array })
items: Item[] = [];
items: ItemType[] = [];
@property({ type: String })
query = "";
@property({ type: Array })
filteredItems: Item[] = [];
filteredItems: ItemType[] = [];
@property({ type: Array })
sortedItems: Item[] = [];
sortedItems: ItemType[] = [];
@property({ type: Boolean })
hideHeader: boolean = false;
hideHeader = false;
@property({ type: String })
dateName = "Date Loaded";
@@ -36,10 +36,14 @@ class ItemIndex extends LitElement {
@state()
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
private _deleting: any = {};
protected _deleting: any = {};
private typeFilter = "";
private indexParams = "";
private get typeFilter() {
return "";
}
private get indexParams() {
return "";
}
constructor() {
super();
@@ -84,7 +88,7 @@ class ItemIndex extends LitElement {
if (
item.sourceUrl.indexOf(this.query) >= 0 ||
item.filename.indexOf(this.query) >= 0 ||
(item.loadUrl && item.loadUrl.indexOf(this.query) >= 0) ||
Boolean(item.loadUrl && item.loadUrl.indexOf(this.query) >= 0) ||
(item.title && item.title.indexOf(this.query) >= 0)
) {
this.filteredItems.push(item);
@@ -99,8 +103,8 @@ class ItemIndex extends LitElement {
throw new Error("Invalid API Response, Retry");
}
const json = await resp.json();
this.items = json.colls.map((item: Item) => {
item.title = item.title || item.filename;
this.items = json.colls.map((item: ItemType) => {
item.title = item.title ?? item.filename;
return item;
});
@@ -285,7 +289,10 @@ class ItemIndex extends LitElement {
<input
type="text"
class="input is-small"
@input="${(e) => (this.query = e.currentTarget.value)}"
@input="${(e: Event) =>
(this.query = (
e.currentTarget as HTMLInputElement
).value)}"
.value="${this.query}"
placeholder="Search by Archive Title or Source"
/>
@@ -299,15 +306,19 @@ class ItemIndex extends LitElement {
?sortDesc="${true}"
.sortKeys="${this.sortKeys}"
.data="${this.filteredItems}"
@sort-changed="${(e) =>
(this.sortedItems = e.detail.sortedData)}"
@sort-changed="${(
e: CustomEvent<{
sortKey: string | null;
sortDesc: boolean | null;
sortedData: ItemType[];
}>,
) => (this.sortedItems = e.detail.sortedData)}"
>
</wr-sorter>
</div>
<div class="coll-list">
${this.sortedItems &&
this.sortedItems.map(
${this.sortedItems?.map(
(item, i) => html`
<div class="coll-block panel-block">
${this.renderItemInfo(item)}
@@ -343,7 +354,7 @@ class ItemIndex extends LitElement {
`;
}
renderItemInfo(item: Item) {
renderItemInfo(item: ItemType) {
return html`<wr-item-info .item=${item}></wr-item-info>`;
}
+23 -23
View File
@@ -4,14 +4,14 @@ import { wrapCss } from "./misc";
import { map } from "lit/directives/map.js";
import prettyBytes from "pretty-bytes";
import type { Item } from "./types";
import type { ItemType } from "./types";
import "./components/labeled-field";
// ===========================================================================
class ItemInfo extends LitElement {
@property({ type: Object })
item!: Item | Record<string, never>;
item: ItemType | Record<string, never> | null = null;
@property({ type: Boolean })
detailed = false;
@@ -90,31 +90,31 @@ class ItemInfo extends LitElement {
return html`
<wr-labeled-field
label="Source"
copy="${item.sourceUrl}"
copy="${item!.sourceUrl}"
class="column is-4"
>${item.sourceUrl &&
(item.sourceUrl.startsWith("http://") ||
item.sourceUrl.startsWith("https://"))
? html` <a href="${item.sourceUrl}">${item.sourceUrl}</a> `
: html` ${item.sourceUrl}`}
${item.sourceUrl && item.sourceUrl.startsWith("googledrive://")
? html` <i>(${item.filename})</i>`
>${item!.sourceUrl &&
(item!.sourceUrl.startsWith("http://") ||
item!.sourceUrl.startsWith("https://"))
? html` <a href="${item!.sourceUrl}">${item!.sourceUrl}</a> `
: html` ${item!.sourceUrl}`}
${item!.sourceUrl && item!.sourceUrl.startsWith("googledrive://")
? html` <i>(${item!.filename})</i>`
: nothing}
</wr-labeled-field>
${showItemID
? html`<wr-labeled-field
label="Archived Item ID"
.copy=${item.coll}
.copy=${item!.coll}
class="column"
>
${item.coll || "No ID"}
${item!.coll || "No ID"}
</wr-labeled-field>`
: nothing}
<wr-labeled-field label="Date Loaded" class="column is-2">
${item.ctime ? new Date(item.ctime).toLocaleString() : nothing}
${item!.ctime ? new Date(item!.ctime).toLocaleString() : nothing}
</wr-labeled-field>
<wr-labeled-field label="Total Size" class="column is-2">
${prettyBytes(Number(item.totalSize || item.size || 0))}
${prettyBytes(Number(item!.totalSize || item!.size || 0))}
</wr-labeled-field>
`;
}
@@ -125,8 +125,8 @@ class ItemInfo extends LitElement {
return html` <div class="columns">
<div class="column col-title is-4">
<span class="subtitle has-text-weight-bold">
<a href="?source=${encodeURIComponent(item.sourceUrl)}"
>${item.name || item.title || item.filename}</a
<a href="?source=${encodeURIComponent(item!.sourceUrl)}"
>${item!.name || item!.title || item!.filename}</a
>
</span>
</div>
@@ -145,26 +145,26 @@ class ItemInfo extends LitElement {
datapackageHash,
publicKey,
software,
} = this.item.verify || {};
} = this.item!.verify || {};
const certFingerprintUrl = certFingerprint
? `https://crt.sh/?q=${certFingerprint}`
: "";
return html` <div class="columns">
${item.name || item.title
${item!.name || item!.title
? html`<wr-labeled-field label="Title" class="column">
${item.name || item.title}
${item!.name || item!.title}
</wr-labeled-field>`
: nothing}
<wr-labeled-field label="Filename" class="column">
${item.filename}
${item!.filename}
</wr-labeled-field>
${item.resources
${item!.resources
? html`<wr-labeled-field label="Files" class="column">
<ol style="padding: revert">
${map(
item.resources,
item!.resources,
(resource) =>
html`<li>
<a href="${resource.path}">${resource.name + "\n"}</a>
@@ -222,7 +222,7 @@ class ItemInfo extends LitElement {
</wr-labeled-field>
<wr-labeled-field label="Loading Mode" class="column">
${item.onDemand ? "Download On-Demand" : "Fully Local"}
${item!.onDemand ? "Download On-Demand" : "Fully Local"}
</wr-labeled-field>
</div>`;
}
+34 -43
View File
@@ -1,4 +1,10 @@
import { LitElement, html, css, type PropertyValues } from "lit";
import {
LitElement,
html,
css,
type PropertyValues,
type TemplateResult,
} from "lit";
import { property } from "lit/decorators.js";
import { ref, createRef, type Ref } from "lit/directives/ref.js";
import type {
@@ -51,7 +57,7 @@ import fasCaretDown from "@fortawesome/fontawesome-free/svgs/solid/caret-down.sv
import { RWPEmbedReceipt } from "./embed-receipt";
import Split from "split.js";
import type { Item as ItemType } from "./types";
import type { ItemType, URLResource } from "./types";
import type { Replay } from "./replay";
import { ifDefined } from "lit/directives/if-defined.js";
@@ -76,6 +82,7 @@ export type LoadInfo = {
swError?: string;
newFullImport?: unknown;
name?: string;
importCollId?: string;
};
// ===========================================================================
@@ -175,14 +182,13 @@ class Item extends LitElement {
private observer?: IntersectionObserver;
private archiveInfoDialog: Ref<SlDialog> = createRef();
private readonly archiveInfoDialog: Ref<SlDialog> = createRef();
private readonly tabNames = ["pages", "story", "resources", "info"];
private readonly tabLabels = {
pages: "Pages",
story: "Story",
resources: "URLs",
info: "Archive Info",
};
constructor() {
@@ -214,9 +220,7 @@ class Item extends LitElement {
while (
this.editable &&
this.autoUpdateInterval &&
(!this.itemInfo ||
!this.itemInfo.pages ||
this.itemInfo.pages.length < 100)
(!this.itemInfo?.pages || this.itemInfo.pages.length < 100)
) {
await new Promise((resolve) =>
setTimeout(resolve, this.autoUpdateInterval * 1000),
@@ -283,7 +287,7 @@ class Item extends LitElement {
}
}
if (changedProperties.has("tabData")) {
if (!this.itemInfo || !this.itemInfo.coll) {
if (!this.itemInfo?.coll) {
return;
}
const newHash =
@@ -308,9 +312,8 @@ class Item extends LitElement {
} else {
window.location.hash = this._locationHash;
if (!this.showSidebar) {
const replay = this.renderRoot.querySelector(
"wr-coll-replay",
) as Replay;
const replay =
this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (replay) {
replay.focus();
}
@@ -338,10 +341,8 @@ class Item extends LitElement {
configureSplitter() {
if (this.tabData.url && this.showSidebar) {
const contents = this.renderRoot.querySelector(
"#contents",
) as HTMLElement;
const replay = this.renderRoot.querySelector("wr-coll-replay") as Replay;
const contents = this.renderRoot.querySelector<HTMLElement>("#contents");
const replay = this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (contents && replay && !this.splitter) {
const opts = {
@@ -378,11 +379,11 @@ class Item extends LitElement {
return;
}
let item = this.loadInfo && this.loadInfo.customColl; // @todo(2023-11-06) update customColl -> customItem
let item = this.loadInfo?.customColl; // @todo(2023-11-06) update customColl -> customItem
if (!item) {
const res = await sourceToId(this.sourceUrl);
item = res.coll;
const res = await sourceToId(this.sourceUrl!);
item = res.item;
}
this.item = item;
@@ -406,11 +407,7 @@ class Item extends LitElement {
...json,
};
if (
this.loadInfo &&
this.loadInfo.extraConfig &&
this.loadInfo.extraConfig.headers
) {
if (this.loadInfo?.extraConfig?.headers) {
const headers = this.loadInfo.extraConfig.headers;
await fetch(`${itemApiPrefix}/updateAuth`, {
method: "POST",
@@ -870,10 +867,9 @@ class Item extends LitElement {
const isReplay = !!this.tabData.url;
const isSidebar = isReplay && this.showSidebar;
if (!isReplay && this.tabData && this.tabData.view) {
if (!isReplay && this.tabData?.view) {
const detail = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
title: (this.tabLabels as any)[this.tabData.view],
title: this.tabLabels[this.tabData.view],
replayTitle: false,
};
this.dispatchEvent(
@@ -932,7 +928,7 @@ class Item extends LitElement {
ts="${this.tabData.ts || ""}"
@coll-tab-nav="${this.onItemTabNav}"
id="replay"
@replay-loading="${(e) =>
@replay-loading="${(e: CustomEvent<{ loading: boolean }>) =>
(this.isLoading = e.detail.loading)}"
@replay-favicons="${this.onFavIcons}"
>
@@ -1488,14 +1484,14 @@ class Item extends LitElement {
}
dragStart() {
const replay = this.renderRoot.querySelector("wr-coll-replay") as Replay;
const replay = this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (replay) {
replay.setDisablePointer(true);
}
}
dragEnd() {
const replay = this.renderRoot.querySelector("wr-coll-replay") as Replay;
const replay = this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (replay) {
replay.setDisablePointer(false);
}
@@ -1517,7 +1513,7 @@ class Item extends LitElement {
></wr-item-info>`;
}
renderExtraToolbar(/*isDropdown = false*/) {
protected renderExtraToolbar(/*isDropdown = false*/): "" | TemplateResult<1> {
return "";
}
@@ -1582,9 +1578,7 @@ class Item extends LitElement {
// This is a workaround, since this app's routing doesn't permit normal
// following of in-page anchors.
event.preventDefault();
(
this.renderRoot.querySelector("#skip-replay-target") as HTMLElement
).focus();
this.renderRoot.querySelector<HTMLElement>("#skip-replay-target")?.focus();
}
onKeyDown(event) {
@@ -1702,7 +1696,7 @@ class Item extends LitElement {
onSubmit(event) {
event.preventDefault();
const input = this.renderRoot.querySelector("input") as HTMLInputElement;
const input = this.renderRoot.querySelector<HTMLInputElement>("input")!;
if (input.value) {
this.navigateTo(input.value);
} else {
@@ -1719,12 +1713,12 @@ class Item extends LitElement {
onTimestampDropdownBtnBlur(event: MouseEvent) {
const btn = event.currentTarget as HTMLButtonElement;
const dropdown = btn.closest("sl-dropdown") as SlDropdown;
const dropdown = btn.closest<SlDropdown>("sl-dropdown");
const relatedTarget = event.relatedTarget as HTMLElement;
if (relatedTarget && dropdown.contains(relatedTarget)) {
if (relatedTarget && dropdown?.contains(relatedTarget)) {
return;
}
if (dropdown.open) {
if (dropdown?.open) {
dropdown.hide();
}
}
@@ -1741,9 +1735,7 @@ class Item extends LitElement {
data = { url: value };
if (value === this.tabData.url) {
const replay = this.renderRoot.querySelector(
"wr-coll-replay",
) as Replay;
const replay = this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (replay) {
replay.refresh();
}
@@ -1761,8 +1753,7 @@ class Item extends LitElement {
_stringToParams(value) {
const q = new URLSearchParams(value.slice(RWP_SCHEME.length));
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
const data: any = {};
const data: Partial<URLResource> = {};
data.url = "";
data.ts = "";
@@ -1807,7 +1798,7 @@ class Item extends LitElement {
this.menuActive = false;
if (this.tabData.url) {
const replay = this.renderRoot.querySelector("wr-coll-replay") as Replay;
const replay = this.renderRoot.querySelector<Replay>("wr-coll-replay");
if (replay) {
replay.refresh();
}
+72 -62
View File
@@ -5,7 +5,7 @@ import prettyBytes from "pretty-bytes";
import { parseURLSchemeHostPath } from "./pageutils";
import { property } from "lit/decorators.js";
import { LoadInfo } from "./item";
import type { LoadInfo } from "./item";
// ===========================================================================
class Loader extends LitElement {
@@ -27,7 +27,8 @@ class Loader extends LitElement {
@property({ type: String }) swName?: string;
pingInterval: number | NodeJS.Timer = 0;
fileHandle: FileSystemHandle | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- requestPermission() type mismatch
fileHandle: any = null;
noWebWorker = false;
worker?: Worker | null;
@@ -54,57 +55,71 @@ class Loader extends LitElement {
this.worker = navigator.serviceWorker as unknown as Worker;
}
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;
this.worker.addEventListener(
"message",
(
event: MessageEvent<{
msg_type: string;
name: string;
percent: number;
error?: string;
fileHandle: FileSystemHandle | null;
currentSize?: number;
totalSize?: number;
extraMsg?: string;
}>,
) => {
switch (event.data.msg_type) {
case "collProgress":
if (event.data.name === this.coll) {
this.percent = event.data.percent;
if (event.data.error) {
this.error = event.data.error;
this.state = "errored";
this.errorAllowRetry = true;
this.fileHandle = event.data.fileHandle;
if (this.error === "missing_local_file") {
this.tryFileHandle = false;
} else if (
this.error === "permission_needed" &&
event.data.fileHandle
) {
this.state = "permission_needed";
break;
}
}
}
if (event.data.currentSize && event.data.totalSize) {
this.currentSize = event.data.currentSize;
this.totalSize = event.data.totalSize;
}
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;
}
},
);
}
async doLoad() {
@@ -115,7 +130,7 @@ class Loader extends LitElement {
// const noSWError = getSWErrorMsg();
if (this.loadInfo && this.loadInfo.swError) {
if (this.loadInfo?.swError) {
this.state = "errored";
this.error = this.loadInfo.swError;
this.errorAllowRetry = false;
@@ -124,7 +139,7 @@ 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":
@@ -179,11 +194,7 @@ 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?.recording) {
// @ts-expect-error - TS2322 - Type '"recordingproxy"' is not assignable to type 'undefined'.
type = "recordingproxy";
}
@@ -229,7 +240,7 @@ You can select a file to upload from the main page by clicking the 'Choose File.
return this._gdWait;
}
async onLoadReady(event) {
onLoadReady(event: CustomEvent) {
if (this._gdResolve) {
//const digest = await digestMessage(url, 'SHA-256');
//this.coll = "id-" + digest.slice(0, 12);
@@ -250,7 +261,7 @@ You can select a file to upload from the main page by clicking the 'Choose File.
return;
}
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
if (navigator.serviceWorker?.controller) {
navigator.serviceWorker.controller.postMessage(msg);
if (this.pingInterval) {
clearInterval(this.pingInterval);
@@ -260,7 +271,7 @@ You can select a file to upload from the main page by clicking the 'Choose File.
updated(changedProperties: PropertyValues<this>) {
if (
(this.sourceUrl && changedProperties.has("sourceUrl")) ||
Boolean(this.sourceUrl && changedProperties.has("sourceUrl")) ||
changedProperties.has("tryFileHandle")
) {
this.doLoad();
@@ -410,8 +421,7 @@ You can select a file to upload from the main page by clicking the 'Choose File.
}
async onAskPermission() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await (this.fileHandle as any)?.requestPermission({
const result = await this.fileHandle?.requestPermission({
mode: "read",
});
if (result === "granted") {
+3 -4
View File
@@ -1,10 +1,9 @@
"use strict";
import { LitElement, html, css, unsafeCSS } from "lit";
import { LitElement, html, css, unsafeCSS, type CSSResultGroup } from "lit";
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
import { styleMap } from "lit/directives/style-map.js";
// @ts-expect-error - TS2307 - Cannot find module '../assets/main.scss' or its corresponding type declarations.
import allCssRaw from "../assets/main.scss";
import rwpLogo from "../assets/logo.svg";
@@ -14,7 +13,7 @@ const replayPrefix = "./w";
// ===========================================================================
const allCss = unsafeCSS(allCssRaw);
function wrapCss(custom) {
function wrapCss(custom: CSSResultGroup): CSSResultGroup {
return [allCss, custom];
}
@@ -22,7 +21,7 @@ const IS_APP =
// @ts-expect-error - TS2339 - Property 'IS_APP' does not exist on type 'Window & typeof globalThis'.
window.IS_APP ||
// @ts-expect-error - TS2551 - Property 'electron' does not exist on type 'Window & typeof globalThis'. Did you mean 'Electron'? | TS2551 - Property 'electron' does not exist on type 'Window & typeof globalThis'. Did you mean 'Electron'?
(window.electron && window.electron.IS_APP) ||
window.electron?.IS_APP ||
window.matchMedia("(display-mode: standalone)").matches;
const VERSION = __VERSION__;
+21 -17
View File
@@ -8,6 +8,7 @@ import "keyword-mark-element/lib/keyword-mark.js";
import { getReplayLink } from "./pageutils";
import { wrapCss } from "./misc";
import type { URLResource } from "./types";
// ===========================================================================
class PageEntry extends LitElement {
@@ -18,8 +19,7 @@ class PageEntry extends LitElement {
textSnippet: string | null = "";
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
page: any = null;
page: URLResource | null = null;
@property({ type: String })
replayPrefix = "";
@@ -191,8 +191,8 @@ class PageEntry extends LitElement {
}
render() {
const p = this.page;
const date = this.page.date;
const p = this.page!;
const date = this.page!.date;
const hasSize = typeof p.size === "number";
@@ -231,8 +231,8 @@ class PageEntry extends LitElement {
@click="${this.onReplay}"
href="${getReplayLink(
"pages",
this.page.url,
this.page.timestamp,
this.page!.url,
this.page!.timestamp!,
)}"
>
<p class="is-size-6 has-text-weight-bold has-text-link text">
@@ -289,19 +289,23 @@ class PageEntry extends LitElement {
return html`<img
class="thumbnail"
@error=${() => (this.thumbnailValid = false)}
src=${`${this.replayPrefix}/${this.page.timestamp}id_/urn:thumbnail:${this.page.url}`}
src=${`${this.replayPrefix}/${this.page!.timestamp}id_/urn:thumbnail:${
this.page!.url
}`}
loading="lazy"
/>`;
}
private renderFavicon() {
if (!this.iconValid || !this.page.favIconUrl) {
if (!this.iconValid || !this.page!.favIconUrl) {
return;
}
return html`<img
class="favicon"
@error=${() => (this.iconValid = false)}
src=${`${this.replayPrefix}/${this.page.timestamp}id_/${this.page.favIconUrl}`}
src=${`${this.replayPrefix}/${this.page!.timestamp}id_/${
this.page!.favIconUrl
}`}
loading="lazy"
/>`;
}
@@ -309,13 +313,13 @@ class PageEntry extends LitElement {
updateSnippet() {
const oldVal = this.textSnippet;
if (!this.query || !this.page.text) {
if (!this.query || !this.page!.text) {
this.textSnippet = null;
this.requestUpdate("textSnippet", oldVal);
return;
}
let textContent = this.page.text;
let textContent = this.page!.text;
let query = this.query;
let inx = textContent.indexOf(this.query);
@@ -351,22 +355,22 @@ class PageEntry extends LitElement {
this.requestUpdate("textSnippet", oldVal);
}
onReplay(event, reload = false) {
onReplay(event: Event, reload = false) {
event.preventDefault();
const data = {
url: this.page.url,
ts: this.page.timestamp,
url: this.page!.url,
ts: this.page!.timestamp,
};
this.sendChangeEvent(data, reload);
return false;
}
onReload(event) {
onReload(event: Event) {
return this.onReplay(event, true);
}
sendChangeEvent(data, reload) {
sendChangeEvent(data, reload: boolean) {
this.dispatchEvent(
new CustomEvent("coll-tab-nav", {
bubbles: true,
@@ -382,7 +386,7 @@ class PageEntry extends LitElement {
}
onSendSelToggle(event) {
const page = this.page.id;
const page = this.page!.id;
const selected = event.currentTarget.checked;
this.dispatchEvent(
new CustomEvent("sel-page", { detail: { page, selected } }),
+91 -89
View File
@@ -1,7 +1,8 @@
"use strict";
import { LitElement, html, css, unsafeCSS } from "lit";
import { LitElement, html, css, unsafeCSS, type PropertyValues } from "lit";
import { property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { wrapCss, clickOnSpacebarPress } from "./misc";
import ndjson from "fetch-ndjson";
@@ -16,31 +17,28 @@ import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
import type { Sorter } from "./sorter";
import type { PageEntry } from "./pageentry";
import type { Id, Index } from "flexsearch";
import type { ItemType, URLResource } from "./types";
// ===========================================================================
class Pages extends LitElement {
@property({ type: Array })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
filteredPages: any[] = [];
filteredPages: URLResource[] = [];
@property({ type: Array })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
sortedPages: any[] = [];
sortedPages: URLResource[] = [];
@property({ type: String })
query = "";
@property()
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
flex: any = null;
@property({ attribute: false })
flex: Index | null = null;
@property({ attribute: false })
textPages: URLResource[] | null = null;
@property()
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
textPages: any = null;
@property()
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
newQuery: any = null;
newQuery: string | null = null;
@property({ type: Boolean })
loading = false;
@@ -55,7 +53,7 @@ class Pages extends LitElement {
hasExtraPages = false;
@property({ type: Number })
currList: number = 0;
currList = 0;
@property({ type: Boolean })
active = false;
@@ -67,7 +65,7 @@ class Pages extends LitElement {
changeNeeded = false;
@property({ attribute: false })
selectedPages = new Set();
selectedPages = new Set<number>();
@property({ type: Boolean })
menuActive = false;
@@ -88,20 +86,16 @@ class Pages extends LitElement {
ts = "";
@property({ type: Boolean })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
editing: any = false;
editing = false;
@property({ type: Array })
toDeletePages: Set<number> | number[] | null = null;
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
toDeletePages: any = null;
toDeletePage: URLResource | null = null;
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
toDeletePage: any = null;
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
collInfo: any;
collInfo: ItemType | Record<string, never> | null = null;
@property({ type: Boolean })
allSelected = false;
@@ -109,8 +103,7 @@ class Pages extends LitElement {
@property({ type: String })
defaultKey = "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
private _ival: any;
private _ival: number | undefined;
static get sortKeys() {
return [
@@ -137,8 +130,7 @@ class Pages extends LitElement {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
async updated(changedProperties: Map<string, any>) {
async updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("collInfo")) {
this.updateTextSearch();
} else if (changedProperties.has("query")) {
@@ -163,7 +155,8 @@ class Pages extends LitElement {
this.sortKey = "date";
this.sortDesc = true;
}
const sorter = this.renderRoot.querySelector("wr-sorter") as Sorter;
const sorter =
this.renderRoot.querySelector<Sorter<URLResource>>("wr-sorter");
if (sorter) {
sorter.sortKey = this.sortKey;
sorter.sortDesc = this.sortDesc;
@@ -174,21 +167,19 @@ class Pages extends LitElement {
//if (await this.updateComplete) {
const selected = this.renderRoot.querySelector(".current");
if (selected) {
const opts = {
const opts: ScrollIntoViewOptions = {
behavior: "smooth",
block: "nearest",
inline: "nearest",
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
setTimeout(() => selected.scrollIntoView(opts as any), 100);
setTimeout(() => selected.scrollIntoView(opts), 100);
}
//}
}
}
onChangeQuery(event: Event) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
this.newQuery = (event.currentTarget as any).value;
this.newQuery = (event.currentTarget as HTMLInputElement).value;
//this.loading = true;
if (this._ival) {
window.clearTimeout(this._ival);
@@ -206,11 +197,13 @@ class Pages extends LitElement {
this.loading = true;
if (this.flex && this.query && this.textPages) {
const results = await this.flex.searchAsync(this.query, 25);
this.filteredPages = results.map((inx: number) => this.textPages[inx]);
this.filteredPages = results.map(
(inx: Id) => this.textPages![inx as number],
);
} else if (this.showAllPages && this.hasExtraPages) {
this.filteredPages = [...this.textPages];
this.filteredPages = [...this.textPages!];
} else {
this.filteredPages = [...this.collInfo.pages];
this.filteredPages = [...this.collInfo!.pages];
}
if (this.currList !== 0) {
@@ -220,6 +213,9 @@ class Pages extends LitElement {
// normalize the date
for (const page of this.filteredPages) {
const { timestamp, date } = getPageDateTS(page);
if (date == null) {
throw new Error("Page date is null");
}
page.timestamp = timestamp;
page.date = date;
}
@@ -232,7 +228,7 @@ class Pages extends LitElement {
async filterCurated() {
const resp = await fetch(
`${this.collInfo.apiPrefix}/curated/${this.currList}`,
`${this.collInfo!.apiPrefix}/curated/${this.currList}`,
);
const json = await resp.json();
@@ -251,11 +247,11 @@ class Pages extends LitElement {
this.flex = flex;
this.textPages = pages;
this.hasExtraPages =
this.hasExtraPages = Boolean(
this.textPages &&
this.collInfo &&
this.collInfo.pages &&
this.textPages.length > this.collInfo.pages.length;
this.collInfo?.pages &&
this.textPages.length > this.collInfo.pages.length,
);
return Promise.all(
pages.map((page, index) => {
@@ -280,9 +276,9 @@ class Pages extends LitElement {
let count = 0;
try {
const cache = await caches.open("cache:" + this.collInfo.coll);
const cache = await caches.open("cache:" + this.collInfo!.coll);
const indexUrl = `${this.collInfo.apiPrefix}/textIndex`;
const indexUrl = `${this.collInfo!.apiPrefix}/textIndex`;
let resp = await cache.match(indexUrl);
@@ -293,8 +289,7 @@ class Pages extends LitElement {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
const lines: any[] = [];
const lines: unknown[] = [];
for await (const line of ndjson(resp.body!.getReader())) {
if (!line.url) {
@@ -310,7 +305,7 @@ class Pages extends LitElement {
console.warn(e);
} finally {
if (count === 0) {
await this.addPages(this.collInfo.pages);
await this.addPages(this.collInfo!.pages);
}
this.updatingSearch = false;
@@ -577,7 +572,7 @@ class Pages extends LitElement {
role="heading"
aria-level="${this.isSidebar ? "2" : "1"}"
>
Pages in ${this.collInfo.title}
Pages in ${this.collInfo!.title}
</div>
<div class="search-bar notification is-marginless">
${this.isSidebar
@@ -612,7 +607,7 @@ class Pages extends LitElement {
<input
id="titleEdit"
class="input"
value="${this.collInfo.title}"
value="${ifDefined(this.collInfo!.title)}"
@blur="${this.onUpdateTitle}"
/>
</form>
@@ -621,14 +616,14 @@ class Pages extends LitElement {
class="index-bar-title"
@dblclick="${() => (this.editing = true)}"
>
${this.collInfo.name || this.collInfo.title}
${this.collInfo!.name || this.collInfo!.title}
</div>
${this.collInfo.description
${this.collInfo!.description
? html`<div
class="index-bar-description"
@dblclick="${() => (this.editing = true)}"
>
${this.collInfo.description}
${this.collInfo!.description}
</div>`
: html``}`}
${this.editable
@@ -638,8 +633,10 @@ class Pages extends LitElement {
? html` <span class="check-select">
<label class="checkbox">
<input
@change=${(e) =>
(this.showAllPages = e.currentTarget.checked)}
@change=${(e: Event) =>
(this.showAllPages = (
e.currentTarget as HTMLInputElement
).checked)}
type="checkbox"
.checked="${this.showAllPages}"
/>
@@ -659,7 +656,7 @@ class Pages extends LitElement {
${this.renderDownloadMenu()}
</div>`
: ""}
${this.collInfo.lists.length
${this.collInfo!.lists.length
? html`
<p id="filter-label" class="menu-label">Filter By List:</p>
<div class="index-bar-menu menu">
@@ -673,7 +670,7 @@ class Pages extends LitElement {
><i>All Pages</i></a
>
</li>
${this.collInfo.lists.map(
${this.collInfo!.lists.map(
(list) =>
html` <li>
<a
@@ -859,7 +856,12 @@ class Pages extends LitElement {
: html`
<p>
Are you sure you want to delete the
<b>${this.toDeletePages.size}</b> selected pages?
<b
>${Array.isArray(this.toDeletePages)
? new Set(this.toDeletePage).size
: this.toDeletePages.size}</b
>
selected pages?
</p>
`}
<p>This operation can not be undone.</p>
@@ -914,7 +916,7 @@ class Pages extends LitElement {
pid="${p.id}"
@sel-page="${this.onSelectToggle}"
@delete-page="${this.onDeleteConfirm}"
replayPrefix="${this.collInfo.replayPrefix}"
replayPrefix="${this.collInfo!.replayPrefix}"
query="${this.query}"
class="${this.isSidebar ? "sidebar" : ""}"
>
@@ -934,10 +936,8 @@ class Pages extends LitElement {
if (!this.editable) {
return;
}
const input = this.renderRoot.querySelector(
"#titleEdit",
) as HTMLInputElement;
if (!input || !input.value.trim()) {
const input = this.renderRoot.querySelector<HTMLInputElement>("#titleEdit");
if (!input?.value.trim()) {
return;
}
@@ -945,15 +945,16 @@ class Pages extends LitElement {
const body = JSON.stringify({ title });
fetch(`${this.collInfo.apiPrefix}/metadata`, { method: "POST", body }).then(
(res) => {
if (res.status === 200) {
this.dispatchEvent(
new CustomEvent("coll-update", { detail: { title } }),
);
}
},
);
fetch(`${this.collInfo!.apiPrefix}/metadata`, {
method: "POST",
body,
}).then((res) => {
if (res.status === 200) {
this.dispatchEvent(
new CustomEvent("coll-update", { detail: { title } }),
);
}
});
}
onMenu(event) {
@@ -1025,22 +1026,23 @@ class Pages extends LitElement {
selected ? Array.from(this.selectedPages.keys()).join(",") : "all",
);
params.set("format", format);
if (this.collInfo.filename) {
params.set("filename", this.collInfo.filename);
if (this.collInfo!.filename) {
params.set("filename", this.collInfo!.filename);
}
window.location.href = `${this.collInfo.apiPrefix}/dl?` + params.toString();
window.location.href =
`${this.collInfo!.apiPrefix}/dl?` + params.toString();
}
onDeleteConfirm(event) {
const page = event.currentTarget.page;
onDeleteConfirm(event: Event) {
const page = (event.currentTarget as PageEntry).page;
// if page is one of the selected, delete entire selection
if (this.selectedPages.has(page.id)) {
if (this.selectedPages.has(page!.id)) {
this.toDeletePages = this.selectedPages;
this.toDeletePage = null;
} else {
// else, delete just the page
this.toDeletePages = [page.id];
this.toDeletePages = [page!.id];
this.toDeletePage = page;
}
}
@@ -1048,18 +1050,18 @@ class Pages extends LitElement {
async onDeletePages() {
const pageMap = {};
for (const id of this.toDeletePages) {
const p = this.renderRoot.querySelector(
for (const id of this.toDeletePages!) {
const p = this.renderRoot.querySelector<PageEntry>(
`wr-page-entry[pid="${id}"]`,
) as PageEntry;
);
if (p) {
p.deleting = true;
pageMap[id] = p;
}
}
for (const id of this.toDeletePages) {
const resp = await fetch(`${this.collInfo.apiPrefix}/page/${id}`, {
for (const id of this.toDeletePages!) {
const resp = await fetch(`${this.collInfo!.apiPrefix}/page/${id}`, {
method: "DELETE",
});
const json = await resp.json();
@@ -1075,12 +1077,12 @@ class Pages extends LitElement {
continue;
}
const inx = this.collInfo.pages.indexOf(page);
const inx = this.collInfo!.pages.indexOf(page);
if (inx < 0) {
continue;
}
this.collInfo.pages.splice(inx, 1);
this.collInfo!.pages.splice(inx, 1);
}
this.toDeletePages = null;
@@ -1110,7 +1112,7 @@ class Pages extends LitElement {
}
getNoResultsMessage() {
if (!this.collInfo || !this.collInfo.pages.length) {
if (!this.collInfo?.pages.length) {
return html`<span class="fix-text-wrapping"
>No Pages are defined in this archive. The archive may be empty.
<a href="#view=resources">Try browsing by URL</a>.</span
@@ -1140,7 +1142,7 @@ class Pages extends LitElement {
const diff =
element.scrollHeight - element.scrollTop - element.clientHeight;
if (diff < 40) {
const sorter = this.renderRoot.querySelector("wr-sorter") as Sorter;
const sorter = this.renderRoot.querySelector<Sorter>("wr-sorter");
if (sorter) {
sorter.getMore();
}
+12 -10
View File
@@ -1,5 +1,7 @@
import type { URLResource } from "./types";
// ===========================================================================
async function digestMessage(message, hashtype) {
async function digestMessage(message: string, hashtype: AlgorithmIdentifier) {
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest(hashtype, msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
@@ -10,7 +12,7 @@ async function digestMessage(message, hashtype) {
}
// ===========================================================================
function tsToDate(ts) {
function tsToDate(ts: string | null | undefined) {
if (!ts) {
return "";
}
@@ -37,7 +39,7 @@ function tsToDate(ts) {
}
// ===========================================================================
function getDateFromTS(ts: number) {
function getDateFromTS(ts: string | number) {
let date: Date | null = null;
date = new Date(ts);
const timestamp =
@@ -46,7 +48,7 @@ function getDateFromTS(ts: number) {
}
// ===========================================================================
function getPageDateTS(page) {
function getPageDateTS(page: URLResource) {
let date: Date | null = null;
try {
date = new Date(page.ts || page.date);
@@ -60,12 +62,12 @@ function getPageDateTS(page) {
}
// ===========================================================================
function getTS(iso) {
function getTS(iso: string) {
return iso.replace(/[-:T]/g, "").slice(0, 14);
}
// ===========================================================================
function getReplayLink(view, url, ts) {
function getReplayLink(view: string, url: string, ts: string) {
const params = new URLSearchParams();
params.set("view", view);
params.set("url", url);
@@ -74,7 +76,7 @@ function getReplayLink(view, url, ts) {
}
// ===========================================================================
async function sourceToId(url) {
async function sourceToId(url: string) {
try {
new URL(url);
} catch (e) {
@@ -83,14 +85,14 @@ async function sourceToId(url) {
}
const digest = await digestMessage(url, "SHA-256");
const coll = "id-" + digest.slice(0, 12);
return { url, coll };
const item = "id-" + digest.slice(0, 12);
return { url, item };
}
// ===========================================================================
// simple parse of scheme, host, rest of path
// not using URL due to different browser behavior
function parseURLSchemeHostPath(url) {
function parseURLSchemeHostPath(url: string) {
let inx = url.indexOf("://");
let hostInx = 0;
let scheme = "";
+11 -17
View File
@@ -2,16 +2,15 @@ import { LitElement, html, css, type PropertyValues } from "lit";
import { property } from "lit/decorators.js";
import { wrapCss, rwpLogo } from "./misc";
import type { Item } from "./types";
import type { ItemType } from "./types";
// ===========================================================================
class Replay extends LitElement {
@property({ type: Object })
collInfo: Item | Record<string, never> | null = null;
collInfo: ItemType | Record<string, never> | null = null;
@property({ type: String })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
sourceUrl: any = null;
sourceUrl: string | null = null;
// external url set from parent
@property({ type: String })
@@ -34,14 +33,13 @@ class Replay extends LitElement {
title = "";
@property({ type: String })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
iframeUrl: any = null;
iframeUrl: string | null = null;
@property({ type: Boolean })
showAuth = false;
@property({ type: Object })
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- requestPermission() type mismatch
authFileHandle: any = null;
private reauthWait: null | Promise<void> = null;
@@ -164,8 +162,7 @@ class Replay extends LitElement {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
setDisablePointer(disable: any) {
setDisablePointer(disable: boolean) {
const iframe = this.renderRoot.querySelector("iframe");
if (iframe) {
@@ -173,8 +170,7 @@ class Replay extends LitElement {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
onReplayMessage(event: any) {
onReplayMessage(event: MessageEvent) {
const iframe = this.renderRoot.querySelector("iframe");
if (iframe && event.source === iframe.contentWindow) {
@@ -238,14 +234,12 @@ class Replay extends LitElement {
this._loadPoll = window.setInterval(() => {
const iframe = this.renderRoot.querySelector("iframe");
if (
!iframe ||
!iframe.contentDocument ||
!iframe?.contentDocument ||
!iframe.contentWindow ||
(iframe.contentDocument.readyState === "complete" &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
!(iframe.contentWindow as Window & { _WBWombat: any })._WBWombat)
!(iframe.contentWindow as Window & { _WBWombat: unknown })._WBWombat)
) {
this.clearLoading(iframe && iframe.contentWindow);
this.clearLoading(iframe?.contentWindow);
}
}, 5000);
}
@@ -405,7 +399,7 @@ class Replay extends LitElement {
</button>
`
: html` <wr-gdrive
.sourceUrl="${this.sourceUrl}"
.sourceUrl="${this.sourceUrl!}"
state="trymanual"
.reauth="${true}"
@load-ready="${this.onReAuthed}"
+23 -32
View File
@@ -1,44 +1,37 @@
import { LitElement, html, css, type PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { wrapCss } from "./misc";
import fasSortDown from "@fortawesome/fontawesome-free/svgs/solid/sort-down.svg";
import fasSortUp from "@fortawesome/fontawesome-free/svgs/solid/sort-up.svg";
// ===========================================================================
@customElement("wr-sorter")
class Sorter<T = unknown> extends LitElement {
sortedData: T[];
data: T[];
pageResults: number;
numResults: number;
@property({ attribute: false })
sortedData: T[] = [];
sortKey: string | null;
sortDesc: boolean | null;
sortKeys: { key: string; name: string }[] = [];
constructor() {
super();
this.sortedData = [];
this.data = [];
@property({ attribute: false })
data: T[] = [];
this.pageResults = 0;
this.numResults = 0;
@property({ type: String })
id!: string;
this.sortKey = null;
this.sortDesc = null;
}
@property({ type: Number })
pageResults = 0;
static get properties() {
return {
id: { type: String },
@property({ attribute: false })
numResults = 0;
pageResults: { type: Number },
@property({ type: String })
sortKey: string | null = null;
data: { type: Array },
sortedData: { type: Array },
@property({ type: Boolean })
sortDesc: boolean | null = null;
sortKey: { type: String },
sortDesc: { type: Boolean },
};
}
@property({ attribute: false })
sortKeys?: { key: string; name: string }[];
firstUpdated() {
if (this.id) {
@@ -75,7 +68,7 @@ class Sorter<T = unknown> extends LitElement {
this.sortedData = [...this.data];
this.numResults = this.pageResults;
if ((this.sortKey == null || this.sortKey) === "") {
if (this.sortKey === "") {
if (this.sortDesc) {
this.sortedData.reverse();
}
@@ -129,10 +122,10 @@ class Sorter<T = unknown> extends LitElement {
render() {
return html`
<div class="select is-small">
<select id="sort-select" @change=${(e) =>
(this.sortKey = e.currentTarget.value)}>
<select id="sort-select" @change=${(e: Event) =>
(this.sortKey = (e.currentTarget as HTMLSelectElement).value)}>
${this.sortKeys.map(
${this.sortKeys?.map(
(sort) => html`
<option value="${sort.key}" ?selected="${sort.key === this.sortKey}">
Sort By: ${sort.name}
@@ -154,6 +147,4 @@ class Sorter<T = unknown> extends LitElement {
}
}
customElements.define("wr-sorter", Sorter);
export { Sorter };
+64 -88
View File
@@ -7,52 +7,37 @@ import { marked } from "marked";
import { getTS, getReplayLink } from "./pageutils";
import Split from "split.js";
import { ItemInfo } from "./item-info";
import { type ItemType } from "./types";
import { customElement, property } from "lit/decorators.js";
// ===========================================================================
@customElement("wr-coll-story")
class Story extends LitElement {
collInfo: ItemInfo | Record<string, never> | null;
curatedPageMap: Record<string, unknown[]>;
currList: number;
active: boolean;
lastST: number;
clickTime: number;
isSidebar: boolean;
splitDirection: boolean | "vertical" | "horizontal";
constructor() {
super();
@property({ type: Object })
collInfo: ItemType | Record<string, never> | null = null;
this.collInfo = null;
@property({ type: Object })
curatedPageMap: Record<string, unknown[]> = {};
this.curatedPageMap = {};
@property({ type: Number })
currList = 0;
this.currList = 0;
@property({ type: Boolean })
active = false;
this.active = false;
@property({ type: Boolean })
isSidebar = false;
this.lastST = 0;
this.clickTime = 0;
@property({ type: Boolean })
splitDirection: boolean | "vertical" | "horizontal" = false;
this.isSidebar = false;
this.splitDirection = false;
}
lastST = 0;
clickTime = 0;
static get properties() {
return {
collInfo: { type: Object },
private obs!: ResizeObserver;
private splitter: Split.Instance | null = null;
active: { type: Boolean },
curatedPageMap: { type: Object },
currList: { type: Number },
isSidebar: { type: Boolean },
splitDirection: { type: Boolean },
};
}
recalcSplitter(width) {
recalcSplitter(width: number) {
this.splitDirection =
this.isSidebar || width < 769 ? "vertical" : "horizontal";
}
@@ -60,12 +45,10 @@ class Story extends LitElement {
firstUpdated() {
this.recalcSplitter(document.documentElement.clientWidth);
// @ts-expect-error - TS2339 - Property 'obs' does not exist on type 'Story'.
this.obs = new ResizeObserver((entries /*, observer*/) => {
this.recalcSplitter(entries[0].contentRect.width);
});
// @ts-expect-error - TS2339 - Property 'obs' does not exist on type 'Story'.
this.obs.observe(this);
}
@@ -93,47 +76,43 @@ class Story extends LitElement {
}
configureSplitter() {
const sidebar = this.renderRoot.querySelector(".sidebar");
const content = this.renderRoot.querySelector(".main-content");
const sidebar = this.renderRoot.querySelector<HTMLElement>(".sidebar");
const content = this.renderRoot.querySelector<HTMLElement>(".main-content");
// @ts-expect-error - TS2339 - Property 'splitter' does not exist on type 'Story'.
if (this.splitter) {
try {
// @ts-expect-error - TS2339 - Property 'splitter' does not exist on type 'Story'.
this.splitter.destroy();
} catch (e) {
// ignore splitter destory err
}
// @ts-expect-error - TS2339 - Property 'splitter' does not exist on type 'Story'.
this.splitter = null;
}
// @ts-expect-error - TS2339 - Property 'splitter' does not exist on type 'Story'.
if (sidebar && content && !this.splitter) {
const opts = {
const opts: Split.Options = {
sizes: [20, 80],
gutterSize: 4,
direction: this.splitDirection,
direction: this.splitDirection as "horizontal" | "vertical",
};
// @ts-expect-error - TS2339 - Property 'splitter' does not exist on type 'Story'. | TS2322 - Type 'Element' is not assignable to type 'string | HTMLElement'. | TS2322 - Type 'Element' is not assignable to type 'string | HTMLElement'.
this.splitter = Split([sidebar, content], opts);
}
}
async doLoadCurated() {
if (this.collInfo == null) {
return;
}
this.curatedPageMap = {};
const pageMap = {};
// @ts-expect-error - TS2339 - Property 'collInfo' does not exist on type 'Story'.
for (const page of this.collInfo.pages) {
pageMap[page.id] = page;
}
// @ts-expect-error - TS2339 - Property 'collInfo' does not exist on type 'Story'.
for (const curated of this.collInfo.curatedPages) {
if (!this.curatedPageMap[curated.list]) {
this.curatedPageMap[curated.list] = [];
@@ -289,29 +268,21 @@ class Story extends LitElement {
? "is-active"
: ""} menu-label is-size-4"
@click=${this.onClickScroll}
>${
// @ts-expect-error - TS2339 - Property 'collInfo' does not exist on type 'Story'.
this.collInfo.title
}</a
>${this.collInfo?.title}</a
>
<ul class="menu-list">
${
// @ts-expect-error - TS2339 - Property 'collInfo' does not exist on type 'Story'.
this.collInfo.lists.map(
(list) =>
html` <li>
<a
@click=${this.onClickScroll}
href="#list-${list.id}"
data-list="${list.id}"
class="${currListNum === list.id
? "is-active"
: ""}"
>${list.title}</a
>
</li>`,
)
}
${this.collInfo?.lists.map(
(list) =>
html` <li>
<a
@click=${this.onClickScroll}
href="#list-${list.id}"
data-list="${list.id}"
class="${currListNum === list.id ? "is-active" : ""}"
>${list.title}</a
>
</li>`,
)}
</ul>
</li>
</ul>
@@ -338,8 +309,7 @@ class Story extends LitElement {
}
renderLists() {
// @ts-expect-error - TS2339 - Property 'collInfo' does not exist on type 'Story'.
return html` ${this.collInfo.lists.map(
return html` ${this.collInfo?.lists?.map(
(list) => html`
<article id="list-${list.id}">
<div class="content">
@@ -380,11 +350,11 @@ class Story extends LitElement {
</li>`;
}
onReplay(event) {
onReplay(event: Event) {
event.preventDefault();
const data = {
url: event.currentTarget.getAttribute("data-url"),
ts: event.currentTarget.getAttribute("data-ts"),
url: (event.currentTarget as Element).getAttribute("data-url"),
ts: (event.currentTarget as Element).getAttribute("data-ts"),
};
this.sendChangeEvent(data);
return false;
@@ -404,30 +374,35 @@ class Story extends LitElement {
scrollToList() {
// lists are 1 based, 0 is header, 1 is first list
// @ts-expect-error - TS2339 - Property 'currList' does not exist on type 'Story'. | TS2339 - Property 'collInfo' does not exist on type 'Story'.
if (this.currList > this.collInfo.lists.length) {
if (this.currList > (this.collInfo?.lists?.length ?? 0)) {
this.currList = 0;
}
const opts = { behavior: "smooth", block: "nearest", inline: "nearest" };
const opts: ScrollIntoViewOptions = {
behavior: "smooth",
block: "nearest",
inline: "nearest",
};
this.clickTime = new Date().getTime();
// @ts-expect-error - TS2339 - Property 'getElementById' does not exist on type 'HTMLElement | ShadowRoot'. | TS2339 - Property 'currList' does not exist on type 'Story'.
const curr = this.renderRoot.getElementById("list-" + this.currList);
const curr = (this.renderRoot as ShadowRoot).getElementById(
"list-" + this.currList,
);
if (curr) {
curr.scrollIntoView(opts);
}
}
onScroll(event) {
const scrollable = event.currentTarget;
// @ts-expect-error - TS2339 - Property 'getElementById' does not exist on type 'HTMLElement | ShadowRoot'. | TS2339 - Property 'currList' does not exist on type 'Story'.
const curr = this.renderRoot.getElementById("list-" + this.currList);
onScroll(event: Event) {
const scrollable = event.currentTarget as HTMLElement;
const curr = (this.renderRoot as ShadowRoot).getElementById(
"list-" + this.currList,
);
if (!curr) {
return;
}
let next = curr;
let next: Element = curr;
const target = scrollable.offsetTop;
const currST = scrollable.scrollTop;
@@ -461,13 +436,14 @@ class Story extends LitElement {
`a[data-list="${this.currList}"]`,
);
if (sel) {
const opts = { behavior: "smooth", block: "nearest", inline: "nearest" };
// @ts-expect-error - TS2345 - Argument of type '{ behavior: string; block: string; inline: string; }' is not assignable to parameter of type 'boolean | ScrollIntoViewOptions | undefined'.
const opts: ScrollIntoViewOptions = {
behavior: "smooth",
block: "nearest",
inline: "nearest",
};
sel.scrollIntoView(opts);
}
}
}
customElements.define("wr-coll-story", Story);
export { Story };
-29
View File
@@ -1,29 +0,0 @@
import INDEX_HTML from "../index.html";
import { SWReplay } from "@webrecorder/wabac/src/swmain";
import { WorkerLoader } from "@webrecorder/wabac/src/loaders";
if (self.registration) {
const staticData = new Map();
const prefix = self.registration.scope;
staticData.set(prefix, { type: "text/html", content: INDEX_HTML });
staticData.set(prefix + "index.html", {
type: "text/html",
content: INDEX_HTML,
});
const sp = new URLSearchParams(self.location.search);
const defaultConfig = {};
if (sp.get("ruffle") == "1") {
defaultConfig.injectScripts = ["ruffle/ruffle.js"];
}
self.sw = new SWReplay({ staticData, defaultConfig });
} else {
new WorkerLoader(self);
}
+22 -6
View File
@@ -1,4 +1,21 @@
export interface Item {
export type URLResource = {
id: number;
title: string;
url: string;
mime: string;
status: string;
date: Date;
ts: string;
desc?: string;
timestamp?: string;
size: number;
favIconUrl: string;
text?: string;
};
export type Page = URLResource;
export type ItemType = {
filename: string;
sourceUrl: string;
replayPrefix: string;
@@ -26,11 +43,10 @@ export interface Item {
software?: string;
};
onDemand?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
pages?: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO fixme
lists?: any[];
pages: URLResource[];
curatedPages: (URLResource & { list: string })[];
lists: URLResource[];
ctime?: string;
totalSize?: unknown;
size?: number | string;
}
};
+2 -9
View File
@@ -6,15 +6,8 @@ import { getReplayLink } from "./pageutils";
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
import "keyword-mark-element/lib/keyword-mark.js";
import { type Item as ItemType } from "./types";
type URLResource = {
url: string;
mime: string;
status: string;
date: string;
ts: string;
};
import { type ItemType } from "./types";
import { type URLResource } from "./types";
// ===========================================================================
/**
+3 -1
View File
@@ -4,7 +4,9 @@ export function assertNonNullish<T>(
): asserts item is NonNullable<T> {
if (item == null) {
throw new Error(
`Variable \`${itemName}\` was expected to be defined, but found value ${item} instead`,
`Variable \`${itemName}\` was expected to be defined, but found value ${
item as Exclude<null | undefined, T>
} (${typeof item}) instead`,
);
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"noEmit": true
},
"extends": "./tsconfig.json",
"include": ["**/*.ts", "**/*.js", ".*.js"],
"exclude": ["__generated__", "__mocks__", "dist", "ruffle/*.js"]
}
+4 -1
View File
@@ -6,6 +6,9 @@
"moduleResolution": "node",
"allowJs": true,
"strict": true,
"declaration": true,
"declarationMap": true,
"declarationDir": "./dist/types",
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
@@ -34,5 +37,5 @@
],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.js"]
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.d.ts"]
}
+2 -1
View File
@@ -98,8 +98,9 @@ const electronPreloadConfig = (/*env, argv*/) => {
const browserConfig = (/*env, argv*/) => {
const isDevServer = process.env.WEBPACK_SERVE;
/** @type {import('webpack').Configuration['entry']} */
const entry = {
ui: "./src/ui.ts",
ui: "./src/index.ts",
};
const patterns = [{ from: "package.json", to: "_data/package.json" }];
+272 -1006
View File
File diff suppressed because it is too large Load Diff