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