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:
+11
-14
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+46
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
Vendored
-21
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
// ===========================================================================
|
||||
/**
|
||||
|
||||
@@ -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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user