chore (tsc): tsc checks

This commit is contained in:
MickaelK 2024-07-19 01:21:22 +10:00
parent 8332752838
commit 9a45dc000a
60 changed files with 220 additions and 135 deletions

View file

@ -1,6 +1,7 @@
import { toHref } from "../lib/skeleton/router.js";
import { animate, slideYOut, slideYIn, opacityOut } from "../lib/animate.js";
import { forwardURLParams } from "../lib/path.js";
import assert from "../lib/assert.js";
import { loadCSS } from "../helpers/loader.js";
import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js";
@ -64,15 +65,14 @@ class ComponentBreadcrumb extends window.HTMLElement {
const tasks = [];
for (let i=0; i<nToAnimate; i++) {
const n = previousChunks.length - i - 1;
const $chunk = this.querySelector(`.component_path-element.n${n}`);
if (!$chunk) throw new Error("component::breadcrumb.js - assertion failed - empty element");
const $chunk = assert.type(this.querySelector(`.component_path-element.n${n}`), window.HTMLElement);
tasks.push(animate($chunk, { time: 100, keyframes: slideYOut(-10) }));
}
await Promise.all(tasks);
}
// STEP2: setup the actual content
this.querySelector(`[data-bind="path"]`).innerHTML = pathChunks.map((chunk, idx) => {
assert.type(this.querySelector(`[data-bind="path"]`), window.HTMLElement).innerHTML = pathChunks.map((chunk, idx) => {
const label = idx === 0 ? (window.CONFIG.name || "Filestash") : chunk;
const link = pathChunks.slice(0, idx + 1).join("/") + "/";
const limitSize = (word, highlight = false) => {
@ -130,8 +130,7 @@ class ComponentBreadcrumb extends window.HTMLElement {
const nToAnimate = pathChunks.length - previousChunks.length;
for (let i=0; i<nToAnimate; i++) {
const n = pathChunks.length - i - 1;
const $chunk = this.querySelector(`.component_path-element.n${n}`);
if (!$chunk) throw new Error("component::breadcrumb.js - assertion failed - empty element");
const $chunk = assert.type(this.querySelector(`.component_path-element.n${n}`), window.HTMLElement);
await animate($chunk, { time: 100, keyframes: slideYIn(-5) });
}
}
@ -141,9 +140,8 @@ class ComponentBreadcrumb extends window.HTMLElement {
let state = this.hasAttribute("indicator");
if (state && this.getAttribute("indicator") !== "false") state = true;
const $indicator = this.querySelector(`[data-bind="path"]`)
.lastChild
.querySelector("span");
let $indicator = assert.type(this.querySelector(`[data-bind="path"]`), window.HTMLElement);
$indicator = assert.type($indicator.lastChild.querySelector("span"), window.HTMLElement);
if (state) {
$indicator.style.opacity = 1;

View file

@ -1,6 +1,7 @@
import { createElement, createRender } from "../lib/skeleton/index.js";
import { navigate, fromHref } from "../lib/skeleton/router.js";
import rxjs, { effect } from "../lib/rx.js";
import assert from "../lib/assert.js";
import { onDestroy } from "../lib/skeleton/lifecycle.js";
import { animate, slideYOut } from "../lib/animate.js";
import { qs } from "../lib/dom.js";
@ -9,7 +10,7 @@ import { init as initBreadcrumb } from "../components/breadcrumb.js";
import ctrlSidebar, { init as initSidebar } from "./sidebar.js";
export default function(ctrl) {
const urlToPath = (pathname = "") => decodeURIComponent(pathname.split("/").filter((chunk, i) => i !== 1).join("/"));
const urlToPath = (pathname = "") => decodeURIComponent(pathname.split("/").filter((_, i) => i !== 1).join("/"));
const $page = createElement(`
<div class="component_filemanager_shell" style="flex-direction:row">
<div data-bind="sidebar"></div>
@ -42,7 +43,9 @@ export default function(ctrl) {
// feature3: key shortcut
const regexStartFiles = new RegExp("^/files/.+");
effect(rxjs.fromEvent(window, "keydown").pipe(
rxjs.filter((e) => regexStartFiles.test(fromHref(location.pathname)) && e.keyCode === 8 && document.activeElement.nodeName !== "INPUT"), // backspace in filemanager
rxjs.filter((e) => regexStartFiles.test(fromHref(location.pathname)) &&
e.keyCode === 8 &&
assert.type(document.activeElement, window.HTMLElement).nodeName !== "INPUT"), // backspace in filemanager
rxjs.tap(() => {
const p = location.pathname.replace(new RegExp("/$"), "").split("/");
p.pop();

View file

@ -1,5 +1,6 @@
import { createFragment } from "../lib/skeleton/index.js";
import { animate, slideYIn } from "../lib/animate.js";
import assert from "../lib/assert.js";
import { loadCSS } from "../helpers/loader.js";
await loadCSS(import.meta.url, "./dropdown.css");
@ -75,9 +76,9 @@ export default class ComponentDropdown extends HTMLDivElement {
`));
const setActive = () => this.classList.toggle("active");
this.querySelector(".dropdown_button").onclick = () => {
assert.type(this.querySelector(".dropdown_button"), window.HTMLElement).onclick = () => {
setActive();
animate(this.querySelector(".dropdown_container"), {
animate(assert.type(this.querySelector(".dropdown_container"), window.HTMLElement), {
time: 100,
keyframes: slideYIn(2),
});

View file

@ -1,4 +1,5 @@
import { loadCSS } from "../helpers/loader.js";
import assert from "../lib/assert.js";
export default class ComponentFab extends window.HTMLButtonElement {
constructor() {
@ -9,7 +10,7 @@ export default class ComponentFab extends window.HTMLButtonElement {
async render($icon) {
await loadCSS(import.meta.url, "./fab.css");
this.querySelector(".content").replaceChildren($icon);
assert.type(this.querySelector(".content"), window.HTMLElement).replaceChildren($icon);
}
}

View file

@ -6,7 +6,7 @@ import { qs, qsa } from "../lib/dom.js";
import { CSS } from "../helpers/loader.js";
export function createModal(opts) {
const $dom = document.body.querySelector("component-modal");
const $dom = assert.type(qs(document.body, "component-modal"), window.HTMLElement);
assert.type($dom, ModalComponent);
return ($node, fn) => $dom.trigger($node, { onQuit: fn, ...opts });
@ -35,14 +35,14 @@ const $modal = createElement(`
`);
class ModalComponent extends window.HTMLElement {
trigger($node, { withButtonsLeft = null, withButtonsRight = null, targetHeight = null, onQuit = (a) => Promise.resolve(a) }) {
trigger($node, { withButtonsLeft = null, withButtonsRight = null, targetHeight = 0, onQuit = (a) => Promise.resolve(a) }) {
const close$ = new rxjs.Subject();
// feature: build the dom
qs($modal, `[data-bind="body"]`).replaceChildren($node);
this.replaceChildren($modal);
qsa($modal, ".component_popup > div.buttons > button").forEach(($button, i) => {
assert.truthy(i >= 0 & i <= 2);
assert.truthy(i >= 0 && i <= 2);
let currentLabel = null;
let buttonIndex = null;
if (i === 0) {

View file

@ -18,7 +18,7 @@ const mv = (from, to) => withVirtualLayer(
);
export default async function ctrlSidebar(render, nRestart = 0) {
if (new URL(location).searchParams.get("nav") === "false") return;
if (new URL(location.toString()).searchParams.get("nav") === "false") return;
else if (document.body.clientWidth < 850) return;
const $page = render(createElement(`

View file

@ -1,9 +1,11 @@
export function loadScript(url: string): Promise<string>;
export function CSS(baseURL: string, ...arrayOfFilenames: string[]): Promise<string>;
export function loadSingleCSS(baseURL: string, filename: string): Promise<string>;
export function loadJS(baseURL: string, path: string, opts?: object): Promise<string>;
export function loadCSS(baseURL: string, ...arrayOfFilenames: string[]): Promise<string>;
export function loadCSSInline(baseURL: string, filename: string): Promise<string>;
export function CSS(baseURL: string, ...arrayOfFilenames: string[]): Promise<string>;
export function init(): Promise<void>;

View file

@ -12,8 +12,8 @@ export async function loadJS(baseURL, path, opts = {}) {
if (document.head.querySelector(`[src="${link.toString()}"]`)) return Promise.resolve();
document.head.appendChild($script);
return new Promise((done) => {
$script.onload = done;
$script.onerror = done;
$script.onload = () => done();
$script.onerror = () => done();
});
}

View file

@ -10,6 +10,8 @@ type AnimationFrames = {
transform?: string;
opacity?: number;
height?: string;
width?: string;
offset?: number;
};
export function transition($node: HTMLElement, opts?: TransitionEnter | TransitionLeave): HTMLElement;
@ -17,6 +19,10 @@ export function transition($node: HTMLElement, opts?: TransitionEnter | Transiti
export function animate($node: HTMLElement | null, opts: {
time: number;
keyframes: AnimationFrames[];
easing?: string;
fill?: string;
onExit?: () => void;
onEnter?: () => void;
}): Promise<void>;
export function slideXIn(dist: number): AnimationFrames[];

View file

@ -1,15 +1,14 @@
export default class assert {
static type(object, type, msg) {
if (object instanceof type) return;
throw new Error(msg || `assertion failed - unexpected type for ${object.toString()}`);
if (!(object instanceof type)) throw new TypeError(msg || `assertion failed - unexpected type for ${object.toString()}`);
return object;
}
static truthy(object, msg) {
if (object) return;
throw new Error(msg || `assertion failed - object is not truthy`);
if (!object) throw new TypeError(msg || `assertion failed - object is not truthy`);
}
static fail(object, msg) {
throw new Error(msg || `assertion failed - ${object}`);
throw new TypeError(msg || `assertion failed - ${object}`);
}
}

View file

@ -37,7 +37,7 @@ class ChromecastManager {
// }
createRequest(mediaInfo, authorization) {
if (!authorization) Promise.error(new Error("Invalid account"));
if (!authorization) Promise.reject(new Error("Invalid account"));
// TODO: it would be much much nicer to set the authorization in an HTTP header
// but this would require to create a custom web receiver app, setup accounts on

View file

@ -1,5 +1,5 @@
export function qs($node: HTMLElement, selector: string);
export function qs($node: HTMLElement | DocumentFragment, selector: string);
export function qsa($node: HTMLElement, selector: string);
export function qsa($node: HTMLElement | DocumentFragment, selector: string);
export function safe(str: string): string;

View file

@ -1 +1,3 @@
export function gid(prefix: string): string;
export function randomString(size: number): string;

View file

@ -7,11 +7,12 @@ import {
switchMapTo, switchMap,
BehaviorSubject, Subject, ReplaySubject,
pipe, share, toArray, distinctUntilChanged, from,
combineLatest, shareReplay, repeat, interval, merge,
debounceTime, delay, concatMap, distinct, scan, throwError,
} from "./vendor/rxjs.min.js";
combineLatest, shareReplay, race, repeat, interval, merge,
debounceTime, debounce, delay, concatMap, distinct, scan, throwError,
zip, animationFrames, retry, forkJoin, skip, takeUntil, timer,
} from "./vendor/rxjs/rxjs.min.js";
import * as rxajax from "./vendor/rxjs-ajax.min.js";
import * as rxajax from "./vendor/rxjs/rxjs-ajax.min.js";
declare const rxjs: {
of: typeof of,
@ -31,6 +32,8 @@ declare const rxjs: {
fromEvent: typeof fromEvent,
delay: typeof delay,
concatMap: typeof concatMap,
animationFrames: typeof animationFrames,
debounce: typeof debounce,
debounceTime: typeof debounceTime,
throwError: typeof throwError,
interval: typeof interval,
@ -48,6 +51,13 @@ declare const rxjs: {
pipe: typeof pipe,
share: typeof share,
scan: typeof scan,
race: typeof race,
retry: typeof retry,
zip: typeof zip,
forkJoin: typeof forkJoin,
skip: typeof skip,
takeUntil: typeof takeUntil,
timer: typeof timer,
};
export default rxjs;
@ -65,3 +75,5 @@ export function stateMutation($node: HTMLElement, attr: string);
export function preventDefault(): typeof tap;
export function onClick($node: HTMLElement): typeof fromEvent;
export function onLoad($node: HTMLElement): typeof fromEvent;

View file

@ -5,6 +5,8 @@ export default function($root: HTMLElement | null, routes: object, opts: object)
export function createElement(str: string): HTMLElement;
export function createFragment(str: string): DocumentFragment;
export function createRender($parent: HTMLElement | null): (HTMLElement) => void;
export function nop(): void

View file

@ -1,5 +1,5 @@
export function settingsGet(initialValues, prefix = "") {
const raw = JSON.parse(localStorage.getItem("settings")) || {};
const raw = JSON.parse(localStorage.getItem("settings") || "{}") || {};
const currentSettings = {};
Object.keys(initialValues).forEach((key) => {
const settingsKey = prefix ? `${prefix}_${key}` : key;
@ -10,7 +10,7 @@ export function settingsGet(initialValues, prefix = "") {
}
export function settingsSave(currentValues, prefix = "") {
const raw = JSON.parse(localStorage.getItem("settings")) || {};
const raw = JSON.parse(localStorage.getItem("settings") || "{}") || {};
Object.keys(currentValues).forEach((key) => {
const settingsKey = prefix ? `${prefix}_${key}` : key;
raw[settingsKey] = currentValues[key];

View file

@ -1,3 +1,4 @@
// @ts-nocheck
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
BufferGeometryLoader,
FileLoader,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
DataTextureLoader,
DataUtils,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
AmbientLight,
AnimationClip,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
AnimationClip,
Bone,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
BufferGeometry,
FileLoader,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
EventDispatcher,
MOUSE,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
BufferAttribute,
BufferGeometry,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
Curve,
Vector3,

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
Vector3,
Vector4

View file

@ -1,3 +1,4 @@
// @ts-nocheck
/*!
fflate - fast JavaScript compression/decompression
<https://101arrowz.github.io/fflate>

View file

@ -1,3 +1,4 @@
// @ts-nocheck
/**
* @license
* Copyright 2010-2023 Three.js Authors

View file

@ -1,3 +1,4 @@
// @ts-nocheck
import {
BufferAttribute,
BufferGeometry,

View file

@ -25,13 +25,13 @@ export async function init() {
selectedLanguage = "zh_tw";
break;
default:
const userLanguage = navigator.language.split("-")[0];
const userLanguage = navigator.language.split("-")[0] || "";
const idx = [
"az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "et",
"eu", "fi", "fr", "gl", "hr", "hu", "id", "is", "it", "ja",
"ka", "ko", "lt", "lv", "mn", "nb", "nl", "pl", "pt", "ro",
"ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh",
].indexOf(navigator.language.split("-")[0]);
].indexOf(userLanguage);
if (idx !== -1) {
selectedLanguage = userLanguage;
}

View file

@ -3,7 +3,7 @@ import { toHref } from "../../lib/skeleton/router.js";
import rxjs, { effect, applyMutation, applyMutations, preventDefault, onClick } from "../../lib/rx.js";
import ajax from "../../lib/ajax.js";
import { qs, qsa, safe } from "../../lib/dom.js";
import { animate, slideYIn, transition } from "../../lib/animate.js";
import { animate, slideYIn, transition, opacityIn } from "../../lib/animate.js";
import { createForm } from "../../lib/form.js";
import { settings_get, settings_put } from "../../lib/settings.js";
import t from "../../locales/index.js";
@ -48,7 +48,7 @@ export default async function(render) {
rxjs.map((conns) => conns.map(({ label, n }) => createElement(`<button data-current="${n}">${safe(label)}</button>`))),
applyMutations($nav, "appendChild"),
rxjs.tap((conns = []) => { if (conns.length > 1) $nav.classList.remove("hidden"); }),
rxjs.tap(() => animate($nav)),
rxjs.tap(() => animate($nav, { time: 250, keyframes: opacityIn() })),
));
// feature2: select a default storage among all the available ones

View file

@ -20,7 +20,7 @@ export default function(render) {
const setState = (newState) => state$.next(newState);
effect(state$.asObservable().pipe(rxjs.mergeMap(({ step, ...state }) => {
if (step === null) return verify(render, { shareID, setState });
if (step === null) return verify(render, { shareID, setState, body: null });
else if (step === "password") return ctrlPassword(render, { shareID, setState });
else if (step === "email") return ctrlEmail(render, { shareID, setState });
else if (step === "code") return ctrlEmailCodeVerification(render, { shareID, setState });
@ -81,7 +81,7 @@ function ctrlEmailCodeVerification(render, { shareID, setState }) {
return ctrlAbstract(render, { shareID, setState, $page });
}
function verify(render, { shareID, setState, body }) {
function verify(_, { shareID, setState, body }) {
return ajax({
method: "POST",
url: `api/share/${shareID}/proof`,
@ -115,7 +115,7 @@ function ctrlAbstract(render, { shareID, setState, $page }) {
applyMutation(qs($page, "component-icon"), "setAttribute"),
// STEP2: attempt to login
rxjs.map(() => ({ type: qs($page, "input").name, value: qs($page, "input").value })),
rxjs.switchMap((creds) => verify(render, { body: creds, shareID, setState }).pipe(rxjs.catchError((err) => {
rxjs.switchMap((creds) => verify(render, { shareID, body: creds, setState }).pipe(rxjs.catchError((err) => {
if (err instanceof AjaxError) {
switch (err.code()) {
case "INTERNAL_SERVER_ERROR":

View file

@ -55,7 +55,7 @@ export default WithShell(async function(render) {
));
// feature: cleanup up the design when navbar is not there
effect(rxjs.of(new URL(location).searchParams.get("nav")).pipe(
effect(rxjs.of(new URL(location.toString()).searchParams.get("nav")).pipe(
rxjs.filter((value) => value === "false"),
rxjs.tap(() => {
$page.parentElement.style.border = "none";

View file

@ -1,11 +1,11 @@
import { getSession } from "../../model/session.js";
class ICache {
async get(path) { throw new Error("NOT_IMPLEMENTED"); }
async get() { throw new Error("NOT_IMPLEMENTED"); }
async store(path) { throw new Error("NOT_IMPLEMENTED"); }
async store() { throw new Error("NOT_IMPLEMENTED"); }
async remove(path, exact = true) { throw new Error("NOT_IMPLEMENTED"); }
async remove() { throw new Error("NOT_IMPLEMENTED"); }
async update(path, fn) {
const data = await this.get(path);
@ -70,7 +70,7 @@ class IndexDBCache extends ICache {
request.onsuccess = (e) => {
done(e.target.result);
};
request.onerror = (e) => err(new Error("INDEXEDDB_NOT_SUPPORTED"));
request.onerror = () => err(new Error("INDEXEDDB_NOT_SUPPORTED"));
});
}
@ -79,9 +79,9 @@ class IndexDBCache extends ICache {
const tx = db.transaction(this.FILE_PATH, "readonly");
const store = tx.objectStore(this.FILE_PATH);
const query = store.get(this._key(path));
return await new Promise((done, error) => {
return await new Promise((done) => {
query.onsuccess = (e) => done(query.result || null);
query.onerror = () => done();
query.onerror = () => done(null);
});
}
@ -123,7 +123,7 @@ class IndexDBCache extends ICache {
cursor.continue();
return;
}
done();
done(null);
};
request.onerror = err;
});
@ -131,7 +131,7 @@ class IndexDBCache extends ICache {
const req = store.delete(key);
return await new Promise((done, err) => {
req.onsuccess = () => done();
req.onsuccess = () => done(null);
req.onerror = err;
});
}

View file

@ -1,6 +1,7 @@
import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js";
import { animate, slideYIn } from "../../lib/animate.js";
import rxjs, { effect, preventDefault } from "../../lib/rx.js";
import assert from "../../lib/assert.js";
import { loadCSS } from "../../helpers/loader.js";
import { qs } from "../../lib/dom.js";
import { ApplicationError } from "../../lib/error.js";
@ -175,7 +176,7 @@ export default async function(render) {
BLOCK_SIZE, COLUMN_PER_ROW, FILE_HEIGHT,
MARGIN,
currentState,
height, setHeight,
/* height, */ setHeight,
$list,
}) => rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }).pipe(
rxjs.map((e) => {
@ -261,7 +262,10 @@ export default async function(render) {
rxjs.tap(() => clearSelection()),
));
effect(rxjs.fromEvent(window, "keydown").pipe(
rxjs.filter((e) => e.key === "a" && (e.ctrlKey || e.metaKey) && (files$.value || []).length > 0 && document.activeElement.tagName !== "INPUT"),
rxjs.filter((e) => e.key === "a" &&
(e.ctrlKey || e.metaKey) &&
(files$.value || []).length > 0 &&
assert.type(document.activeElement, window.HTMLElement).tagName !== "INPUT"),
preventDefault(),
rxjs.tap(() => {
clearSelection();
@ -283,7 +287,7 @@ export default async function(render) {
});
}),
));
effect(getSelection$().pipe(rxjs.tap((a) => {
effect(getSelection$().pipe(rxjs.tap(() => {
for (const $thing of $page.querySelectorAll(".component_thing")) {
const checked = isSelected(parseInt($thing.getAttribute("data-n")));
$thing.classList.add(checked ? "selected" : "not-selected");
@ -310,7 +314,7 @@ function renderEmpty(render, base64Icon) {
<p class="label">${t("There is nothing here")}</p>
</div>
`);
animate(render($page), { keyframes: slideYIn(5) });
animate(render($page), { time: 250, keyframes: slideYIn(5) });
}
export function init() {

View file

@ -66,7 +66,7 @@ export default async function(render) {
return null;
}),
rxjs.filter((val) => val),
rxjs.tap(async({ img, alt, targetName }) => {
rxjs.tap(async({ img, alt }) => {
$icon.setAttribute("src", `data:image/svg+xml;base64,${img}`);
$icon.setAttribute("alt", alt);
$input.value = "";

View file

@ -169,7 +169,7 @@ function componentLeft(render, { $scroll }) {
));
effect(getSelection$().pipe(
rxjs.filter((selections) => lengthSelection() > 1),
rxjs.filter(() => lengthSelection() > 1),
rxjs.map(() => render(createFragment(`
<a target="_blank" ${generateLinkAttributes(expandSelection())}><button data-action="download">
${t("Download")}
@ -184,7 +184,7 @@ function componentLeft(render, { $scroll }) {
return rxjs.from(componentDelete(
createModal(modalOpt),
"remove",
)).pipe(rxjs.mergeMap((val) => {
)).pipe(rxjs.mergeMap(() => {
clearSelection();
return rm(...paths);
}));
@ -217,8 +217,8 @@ function componentRight(render) {
default: throw new Error("NOT_IMPLEMENTED");
}
};
const defaultSort = () => {
return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.SORT}" alt="sort" />`;
const defaultSort = (sort) => { // TODO
return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.SORT}" alt="${sort}" />`;
};
effect(getSelection$().pipe(
rxjs.filter((selections) => selections.length === 0),
@ -320,7 +320,7 @@ function componentRight(render) {
rxjs.filter((e) => (e.ctrlKey || e.metaKey) && e.key === "f"),
preventDefault(),
),
).pipe(rxjs.map(($el) => qs($page, "input").classList.contains("hidden"))),
).pipe(rxjs.map(() => qs($page, "input").classList.contains("hidden"))),
escape$.pipe(rxjs.mapTo(false)),
).pipe(
rxjs.takeUntil(getSelection$().pipe(rxjs.skip(1))),
@ -379,12 +379,12 @@ function componentRight(render) {
effect(getSelection$().pipe(
rxjs.filter((selections) => selections.length >= 1),
rxjs.map((selections) => render(createFragment(`
rxjs.map(() => render(createFragment(`
<button data-bind="clear">
${lengthSelection()} <component-icon name="close"></component-icon>
</button>
`))),
rxjs.mergeMap(($page) => onClick($page, `[data-bind="clear"]`).pipe(
rxjs.mergeMap(($page) => onClick(qs($page, `[data-bind="clear"]`)).pipe(
rxjs.tap(() => clearSelection()),
rxjs.takeUntil(getSelection$().pipe(rxjs.skip(1))),
)),

View file

@ -97,7 +97,7 @@
content: ")";
}
.component_upload .stats_content .file_row .file_state {
width: 40px;
width: 50px;
padding: 3px 0;
}
.component_upload .stats_content .file_row .file_control {

View file

@ -21,13 +21,13 @@ export default async function(render) {
effect(getPermission().pipe(
rxjs.filter(() => calculatePermission(currentPath(), "new-file")),
rxjs.tap((a) => {
rxjs.tap(() => {
const $page = createFragment(`
<div is="component_filezone"></div>
<div is="component_upload_fab"></div>
`);
componentFilezone(createRender($page.children[0]), { workers$ });
componentUploadFAB(createRender($page.children[1]), { workers$ });
componentFilezone(createRender(assert.type($page.children[0], window.HTMLElement)), { workers$ });
componentUploadFAB(createRender(assert.type($page.children[1], window.HTMLElement)), { workers$ });
render($page);
}),
));
@ -61,7 +61,7 @@ function componentUploadFAB(render, { workers$ }) {
function componentFilezone(render, { workers$ }) {
const selector = `[data-bind="filemanager-children"]`;
const $target = document.body.querySelector(selector);
const $target = assert.type(document.body.querySelector(selector), window.HTMLElement);
$target.ondragenter = (e) => {
if (!isNativeFileUpload(e)) return;
@ -178,7 +178,7 @@ function componentUploadQueue(render, { workers$ }) {
if (new Date() - last <= 500) return;
last = new Date();
const speed = workersSpeed.reduce((acc, el) => acc + el, 0);
const $speed = $page.firstElementChild.nextElementSibling.firstElementChild;
const $speed = assert.type($page.firstElementChild.nextElementSibling.firstElementChild, window.HTMLElement);
$speed.textContent = formatSpeed(speed);
};
}(new Array(MAX_WORKERS).fill(0)));
@ -207,7 +207,7 @@ function componentUploadQueue(render, { workers$ }) {
$close.removeEventListener("click", cancel);
break;
case "error":
const $retry = $iconRetry.cloneNode(true);
const $retry = assert.type($iconRetry.cloneNode(true), window.HTMLElement);
updateDOMGlobalTitle($page, t("Error"));
updateDOMGlobalSpeed(nworker, 0);
updateDOMTaskProgress($task, t("Error"));
@ -237,7 +237,7 @@ function componentUploadQueue(render, { workers$ }) {
}
const $task = qs($page, `[data-path="${task.path}"]`);
const exec = task.exec({
error: (err) => updateDOMWithStatus($task, { status: "error", nworker }),
error: () => updateDOMWithStatus($task, { status: "error", nworker, exec: null }),
progress: (progress) => updateDOMTaskProgress($task, formatPercent(progress)),
speed: (speed) => {
updateDOMTaskSpeed($task, speed);
@ -251,7 +251,7 @@ function componentUploadQueue(render, { workers$ }) {
} catch (err) {
updateDOMWithStatus($task, { exec, status: "error", nworker });
}
updateTotal.incrementCompleted(1);
updateTotal.incrementCompleted();
task.done = true;
if (tasks.length === 0 && // no remaining tasks
@ -340,7 +340,7 @@ function workerImplFile({ error, progress, speed }) {
return;
}
virtual.afterSuccess();
done();
done(null);
};
this.xhr.onerror = function(e) {
err(new AjaxError("failed", e, "FAILED"));
@ -360,7 +360,7 @@ function workerImplDirectory({ error, progress }) {
}
cancel() {
this.xhr.abort();
if (this.xhr instanceof XMLHttpRequest) this.xhr.abort();
}
run({ virtual, path }) {
@ -397,7 +397,7 @@ function workerImplDirectory({ error, progress }) {
return;
}
virtual.afterSuccess();
done();
done(null);
};
this.xhr.send(null);
});
@ -418,7 +418,7 @@ async function processFiles(filelist) {
if (file.size % 4096 !== 0) {
return Promise.resolve("file");
}
return new Promise((done, err) => {
return new Promise((done) => {
const reader = new window.FileReader();
const tid = setTimeout(() => reader.abort(), 1000);
reader.onload = () => done("file");
@ -460,7 +460,7 @@ async function processFiles(filelist) {
};
break;
default:
assert.fail(`NOT_SUPPORTED type="${type}"`, type);
assert.fail(type, `NOT_SUPPORTED type="${type}"`);
}
task.virtual.before();
tasks.push(task);
@ -476,7 +476,7 @@ async function processItems(itemList) {
while (queue.length > 0) {
const entry = queue.shift();
const path = basepath + entry.fullPath.substring(1);
let task = null;
let task = {};
if (entry === null) continue;
else if (entry.isFile) {
const entrySize = await new Promise((done) => entry.getMetadata(({ size }) => done(size)));

View file

@ -44,12 +44,12 @@ function renderDesktop(render, removeLabel) {
return ret.toPromise();
}
function renderMobile(render, removeLabel) {
function renderMobile(_, removeLabel) {
return new Promise((done) => {
const value = window.prompt(t("Confirm by typing") + ": " + removeLabel, "");
if (value !== removeLabel) {
return;
}
done();
done(null);
});
}

View file

@ -48,7 +48,7 @@ function renderDesktop(render, filename) {
return ret.toPromise();
}
function renderMobile(render, filename) {
function renderMobile(_, filename) {
return new Promise((done) => {
const value = window.prompt(t("Rename as"), filename);
if (!value || value === filename) {

View file

@ -42,7 +42,7 @@ export default function(render, { path }) {
};
// feature: select
const toggle = (val) => rxjs.mergeMap(($button) => {
const toggle = (val) => rxjs.mergeMap(() => {
state.form = {};
role$.next(role$.value === val ? null : val);
return rxjs.EMPTY;
@ -150,7 +150,7 @@ async function ctrlExistingShare(render, { load, remove, all, formLinks }) {
copyToClipboard(link);
notification.info(t("The link was copied in the clipboard"));
});
qs($share, `[alt="delete"]`).onclick = async(e) => {
qs($share, `[alt="delete"]`).onclick = async() => {
$share.remove();
length -= 1;
if (length === 0) $content.replaceChildren(createElement(`
@ -158,7 +158,7 @@ async function ctrlExistingShare(render, { load, remove, all, formLinks }) {
`));
await remove(shareObj);
};
qs($share, `[alt="edit"]`).onclick = (e) => load(shareObj);
qs($share, `[alt="edit"]`).onclick = () => load(shareObj);
$fragment.appendChild($share);
});
$content.replaceChildren($fragment);
@ -229,7 +229,7 @@ async function ctrlCreateShare(render, { save, formState }) {
};
const tmpl = formTmpl({
renderNode: () => createElement("<div></div>"),
renderLeaf: ({ format, label, type }) => {
renderLeaf: ({ label, type }) => {
if (type !== "enable") return createElement("<label></label>");
const title =
label === "users_enable"
@ -273,7 +273,7 @@ async function ctrlCreateShare(render, { save, formState }) {
// sync editable custom link input with link id
effect(rxjs.fromEvent(qs($form, `[name="url"]`), "keyup").pipe(rxjs.tap((e) => {
id = e.target.value.replaceAll(" ", "-").replace(new RegExp("[^A-Za-z\-]"), "");
qs($form.closest(".component_share"), `input[name="create"]`).value = `${location.origin}${toHref("/s/" + id)}`;
qs(assert.type($form.closest(".component_share"), window.HTMLElement), `input[name="create"]`).value = `${location.origin}${toHref("/s/" + id)}`;
})));
// feature: create a shared link
@ -281,7 +281,8 @@ async function ctrlCreateShare(render, { save, formState }) {
effect(onClick(qs($page, ".shared-link")).pipe(
rxjs.first(),
rxjs.switchMap(async() => {
const body = [...new FormData(document.querySelector(".component_share form"))].reduce((acc, [key, value]) => {
const body = [...new FormData(assert.type(qs(document.body, ".component_share form"), window.HTMLFormElement))]
.reduce((acc, [key, value]) => {
if (value && key.slice(-7) !== "_enable") acc[key] = value;
return acc;
}, { id });

View file

@ -76,7 +76,7 @@ export const mv = (from, to) => ajax({
handleError,
);
export const save = (path) => rxjs.of(null).pipe(rxjs.delay(1000));
export const save = () => rxjs.of(null).pipe(rxjs.delay(1000));
export const ls = (path) => {
const lsFromCache = (path) => rxjs.from(fscache().get(path));
@ -101,7 +101,7 @@ export const ls = (path) => {
rxjs.merge(
rxjs.of(null),
rxjs.merge(rxjs.of(null), rxjs.fromEvent(window, "keydown").pipe( // "r" shorcut
rxjs.filter((e) => e.keyCode === 82 && document.activeElement.tagName !== "INPUT"),
rxjs.filter((e) => e.keyCode === 82 && assert.type(document.activeElement, window.HTMLElement).tagName !== "INPUT"),
)).pipe(rxjs.switchMap(() => lsFromHttp(path))),
),
).pipe(

View file

@ -27,11 +27,6 @@ const mutationFiles$ = new rxjs.BehaviorSubject({
// "/home/": [{ name: "test", fn: (file) => file, ...]
});
window.debug = () => {
console.log("VIRTUAL", JSON.stringify(virtualFiles$.value, null, 4));
console.log("MUTATION", JSON.stringify(mutationFiles$.value));
};
class IVirtualLayer {
before() { throw new Error("NOT_IMPLEMENTED"); }
async afterSuccess() { throw new Error("NOT_IMPLEMENTED"); }
@ -73,7 +68,7 @@ export function touch(path) {
hooks.mutation.emit({ op: "touch", path: basepath });
}
async afterError(err, caught) {
async afterError() {
statePop(virtualFiles$, basepath, filename);
return rxjs.of(fscache().remove(basepath)).pipe(
rxjs.mergeMap(() => rxjs.EMPTY),
@ -110,7 +105,7 @@ export function mkdir(path) {
hooks.mutation.emit({ op: "mkdir", path: basepath });
}
async afterError(err, caught) {
async afterError() {
statePop(virtualFiles$, basepath, dirname);
return rxjs.of(fscache().remove(basepath)).pipe(
rxjs.mergeMap(() => rxjs.EMPTY),

View file

@ -56,7 +56,7 @@ export function createThing({
name = "",
type = "N/A",
time = 0,
path = null,
path = "",
size = 0,
loading = false,
link = "",
@ -67,8 +67,7 @@ export function createThing({
}) {
const [, ext] = formatFile(name);
const mime = TYPES.MIME[ext.toLowerCase()];
const $thing = $tmpl.cloneNode(true);
assert.type($thing, window.HTMLElement);
const $thing = assert.type($tmpl.cloneNode(true), window.HTMLElement);
// you might wonder why don't we use querySelector to nicely get the dom nodes? Well,
// we're in the hot path, better performance here is critical to get 60FPS.

View file

@ -0,0 +1,5 @@
interface Window {
WaveSurfer: {
create: (options: any) => any;
};
}

View file

@ -18,7 +18,7 @@ const STATUS_PLAYING = "PLAYING";
const STATUS_PAUSED = "PAUSED";
// const STATUS_BUFFERING = "BUFFERING";
export default function(render, { mime }) {
export default function(render) {
const $page = createElement(`
<div class="component_audioplayer">
<component-menubar></component-menubar>

View file

@ -36,7 +36,7 @@ export default async function(render) {
const id = setInterval(() => {
if (/download=yes/.test(document.cookie)) return;
clearInterval(id);
done();
done(null);
}, 200);
})),
rxjs.tap(() => setLoading(false)),

View file

@ -0,0 +1,5 @@
interface Window {
ePub: {
Book: new (options: any) => any;
};
}

View file

@ -1,6 +1,7 @@
import { createElement, onDestroy } from "../../lib/skeleton/index.js";
import rxjs, { effect } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import assert from "../../lib/assert.js";
import { loadJS, loadCSS } from "../../helpers/loader.js";
import { createLoader } from "../../components/loader.js";
import ctrlError from "../ctrl_error.js";
@ -46,7 +47,7 @@ export default function(render) {
book.open(getDownloadUrl());
await new Promise((done) => rendition.hooks.render.register(() => {
rendition$.next(rendition);
done();
done(null);
}));
}),
removeLoader,
@ -59,7 +60,7 @@ export default function(render) {
effect(setup$.pipe(
rxjs.mergeMap(() => rxjs.merge(
rxjs.fromEvent(document, "keydown"),
rendition$.pipe(rxjs.mergeMap(() => rxjs.fromEvent(qs(document, "iframe").contentDocument.body, "keydown"))),
rendition$.pipe(rxjs.mergeMap(() => rxjs.fromEvent(assert.type(qs(document.body, "iframe"), window.HTMLElement).contentDocument.body, "keydown"))),
)),
rxjs.map((e) => {
switch (e.code) {

View file

@ -0,0 +1,9 @@
interface Window {
CodeMirror: {
(element: HTMLElement, options: any): any;
__mode: string;
commands: {
save: (editor: any) => void;
};
};
}

View file

@ -85,7 +85,7 @@ export default async function(render, { acl$ }) {
editor.getWrapperElement().setAttribute("mode", mode);
if (!("ontouchstart" in window)) editor.focus();
if (config["editor"] === "emacs") editor.addKeyMap({
"Ctrl-X Ctrl-C": (cm) => window.history.back(),
"Ctrl-X Ctrl-C": () => window.history.back(),
});
onDestroy(() => editor.clearHistory());
$dom.menubar().classList.remove("hidden");
@ -133,7 +133,7 @@ export default async function(render, { acl$ }) {
// feature4: save
effect(setup$.pipe(
rxjs.mergeMap((editor) => new rxjs.Observable((observer) => {
rxjs.mergeMap(() => new rxjs.Observable((observer) => {
window.CodeMirror.commands.save = (cm) => observer.next(cm);
})),
rxjs.mergeMap((cm) => {
@ -152,7 +152,7 @@ export default async function(render, { acl$ }) {
// feature5: save on exit
effect(setup$.pipe(
rxjs.tap((cm) => window.history.block = async(href) => {
rxjs.tap((cm) => window.history.block = async() => {
const block = qs(document.body, "component-breadcrumb").hasAttribute("indicator");
if (block === false) return false;
const userAction = await new Promise((done) => {
@ -213,11 +213,13 @@ export function init() {
function loadMode(ext) {
let mode = "text";
let before = Promise.resolve();
let before = Promise.resolve(null);
if (ext === "org" || ext === "org_archive") {
mode = "orgmode";
before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/xml-fold.js").then(() => loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchtags.js"));
before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/xml-fold.js")
.then(() => loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchtags.js"))
.then(() => Promise.resolve(null));
} else if (ext === "sh") mode = "shell";
else if (ext === "py") mode = "python";
else if (ext === "html" || ext === "htm") {
@ -226,7 +228,7 @@ function loadMode(ext) {
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/xml/xml.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/javascript/javascript.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/css/css.js"),
]);
]).then(() => Promise.resolve(null));
} else if (ext === "css") mode = "css";
else if (ext === "less" || ext === "scss" || ext === "sass") mode = "sass";
else if (ext === "js" || ext === "json") mode = "javascript";
@ -237,7 +239,7 @@ function loadMode(ext) {
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/xml/xml.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/javascript/javascript.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/css/css.js"),
]);
]).then(() => Promise.resolve(null));
} else if (ext === "elm") mode = "elm";
else if (ext === "erl") mode = "erlang";
else if (ext === "go") mode = "go";
@ -248,14 +250,14 @@ function loadMode(ext) {
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/gfm/gfm.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/yaml/yaml.js"),
loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/overlay.js"),
]);
]).then(() => Promise.resolve(null));
} else if (ext === "pl" || ext === "pm") mode = "perl";
else if (ext === "clj") mode = "clojure";
else if (ext === "el" || ext === "lisp" || ext === "cl" ||
ext === "emacs") mode = "commonlisp";
else if (ext === "dockerfile") {
mode = "dockerfile";
before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/simple.js");
before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/simple.js").then(() => Promise.resolve(null));
} else if (ext === "R") mode = "r";
else if (ext === "makefile") mode = "cmake";
else if (ext === "rb") mode = "ruby";
@ -275,7 +277,7 @@ function loadMode(ext) {
return before.then(() => loadJS(import.meta.url, `./application_editor/${mode}.js`, { type: "module" }))
.catch(() => loadJS(import.meta.url, "./application_editor/text.js", { type: "module" }))
.then((module) => Promise.resolve(mode));
.then(() => Promise.resolve(mode));
}
function loadKeybinding(editor) {

View file

@ -78,7 +78,7 @@ export default function(render) {
),
).pipe(
rxjs.map((originalState) => {
const smod = (key, value) => value || undefined;
const smod = (_, value) => value || undefined;
return JSON.stringify(formObjToJSON(originalState), smod) !== JSON.stringify(formState(), smod);
}),
rxjs.mergeMap(async(isSaveButtonVisible) => {

View file

@ -1,5 +1,6 @@
import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect } from "../../lib/rx.js";
import assert from "../../lib/assert.js";
export default function(render) {
const $component = createElement(`
@ -9,6 +10,6 @@ export default function(render) {
effect(rxjs.fromEvent(document, "keypress").pipe(
rxjs.filter((e) => e.key === "i"),
rxjs.tap(() => $component.parentElement.classList.toggle("open")),
rxjs.tap(() => assert.type($component.parentElement, window.HTMLElement).classList.toggle("open")),
));
}

View file

@ -0,0 +1,11 @@
interface Window {
pdfjsLib: {
getDocument: (url: string) => { promise: Promise<any> };
GlobalWorkerOptions: {
workerSrc: string;
};
// Add other properties and methods of pdfjsLib as needed
};
env?: string;
chrome: object;
}

View file

@ -4,9 +4,8 @@ import { animate, slideYIn } from "../../lib/animate.js";
import { loadCSS, loadJS } from "../../helpers/loader.js";
import { qs, qsa } from "../../lib/dom.js";
import { settings_get, settings_put } from "../../lib/settings.js";
import assert from "../../lib/assert.js";
import { ApplicationError } from "../../lib/error.js";
import assert from "../../lib/assert.js";
import Hls from "../../lib/vendor/hlsjs/hls.js";
import ctrlError from "../ctrl_error.js";
@ -170,14 +169,14 @@ export default function(render, { mime }) {
$video.appendChild($source);
return [{ ...sources[i], type: "video/mp4" }];
}
hls.loadSource(sources[i].src, sources[i].type);
hls.loadSource(sources[i].src);
}
hls.attachMedia($video);
return sources;
}),
rxjs.mergeMap((sources) => rxjs.merge(
rxjs.fromEvent($video, "loadeddata"),
...[...qsa($page, "source")].map(($source) => rxjs.fromEvent($source, "error").pipe(rxjs.tap((err) => {
...[...qsa($page, "source")].map(($source) => rxjs.fromEvent($source, "error").pipe(rxjs.tap(() => {
throw new ApplicationError("NOT_SUPPORTED", JSON.stringify({ mime, sources }, null, 2));
}))),
)),
@ -361,7 +360,7 @@ export default function(render, { mime }) {
if ($video.buffered.length !== $container.children.length) {
$container.innerHTML = "";
const $fragment = document.createDocumentFragment();
Array.apply(null, { length: $video.buffered.length })
Array.from({ length: $video.buffered.length })
.map(() => $fragment.appendChild(createElement(`
<div className="progress-buffer" style=""></div>
`)));

View file

@ -1,7 +1,7 @@
export function formatTimecode(seconds) {
return String(parseInt(seconds / 60)).padStart(2, "0") +
return String(Math.floor(seconds / 60)).padStart(2, "0") +
":" +
String(parseInt(seconds % 60)).padStart(2, "0");
String(Math.floor(seconds % 60)).padStart(2, "0");
}
// TODO: abstract setVolume, setSeek and setStatus

View file

@ -20,12 +20,13 @@ export default class ComponentMenubar extends window.HTMLElement {
</div>
`;
if (new URLSearchParams(location.search).get("nav") === "false") {
this.firstElementChild.classList.add("inherit-width");
const $container = assert.type(this.firstElementChild, window.HTMLElement);
$container.classList.add("inherit-width");
}
}
async connectedCallback() {
const $title = this.querySelector(".titlebar");
const $title = assert.type(this.querySelector(".titlebar"), window.HTMLElement);
this.timeoutID = setTimeout(() => animate($title, {
time: 250,
keyframes: slideYIn(2),
@ -38,7 +39,7 @@ export default class ComponentMenubar extends window.HTMLElement {
}
render(buttons) {
const $item = this.querySelector(".action-item");
const $item = assert.type(this.querySelector(".action-item"), window.HTMLElement);
for (let i=buttons.length-1; i>=0; i--) {
$item.appendChild(buttons[i]);
}

12
public/global.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
interface Window {
overrides: {
[key: string]: any;
"xdg-open"?: (mime: string) => void;
};
CONFIG: Config;
}
interface Config {
[key: string]: any;
thumbnailer: string[];
}

View file

@ -25,15 +25,16 @@
"target": "es2022",
"typeRoots": []
},
"include": ["assets/boot/*.js", "assets/pages/*.js"],
"include": [
"assets/boot/*.js",
"assets/pages/*.js",
"global.d.ts"
],
"exclude": [
"**/*.test.js",
"assets/worker/sw_cache.js",
"coverage",
"vite.config.js",
"jest.setup.js",
"assets/lib/vendor/**",
"assets/lib/vendor/hlsjs/hls.js",
"assets/lib/vendor/three/*.js"
"jest.setup.js"
]
}