chore (maintenance): tsconfig setup

This commit is contained in:
MickaelK 2024-08-14 08:33:02 +10:00
parent 28b645fb19
commit d6613c4452
67 changed files with 522 additions and 416 deletions

View file

@ -11,7 +11,7 @@ export default async function main() {
window.dispatchEvent(new window.Event("pagechange"));
} catch (err) {
console.error(err);
const msg = window.navigator.onLine === false ? "OFFLINE" : (err.message || "CAN'T LOAD");
const msg = window.navigator.onLine === false ? "OFFLINE" : (err instanceof Error && err.message) || "CAN'T LOAD";
report("boot::" + msg, err, location.href);
$error(msg);
}

View file

@ -12,7 +12,6 @@ export default async function main() {
setup_translation(),
setup_xdg_open(),
setup_device(),
// setup_sw(), // TODO
setup_blue_death_screen(),
setup_history(),
setup_polyfill(),
@ -26,7 +25,7 @@ export default async function main() {
window.dispatchEvent(new window.Event("pagechange"));
} catch (err) {
console.error(err);
const msg = window.navigator.onLine === false ? "OFFLINE" : (err.message || "CAN'T LOAD");
const msg = window.navigator.onLine === false ? "OFFLINE" : (err instanceof Error && err.message) || "CAN'T LOAD";
report(msg, err, location.href);
$error(msg);
}
@ -69,23 +68,6 @@ async function setup_device() {
});
}
async function setup_sw() { // eslint-disable-line no-unused-vars
if (!("serviceWorker" in window.navigator)) return;
if (window.navigator.userAgent.indexOf("Mozilla/") !== -1 &&
window.navigator.userAgent.indexOf("Firefox/") !== -1 &&
window.navigator.userAgent.indexOf("Gecko/") !== -1) {
// Firefox was acting weird with service worker so we disabled it
// see: https://github.com/mickael-kerjean/filestash/issues/255
return;
}
try {
await window.navigator.serviceWorker.register("/sw_cache.js");
} catch (err) {
report("ServiceWorker registration failed", err);
}
}
async function setup_blue_death_screen() {
window.onerror = function(msg, url, lineNo, colNo, error) {
report(msg, error, url, lineNo, colNo);
@ -116,7 +98,7 @@ async function setup_history() {
}
async function setup_title() {
document.title = window.CONFIG.name || "Filestash";
document.title = window.CONFIG["name"] || "Filestash";
}
async function setup_polyfill() {

View file

@ -13,10 +13,10 @@ const mv = (from, to) => withVirtualLayer(
mvVL(from, to),
);
class ComponentBreadcrumb extends window.HTMLElement {
class ComponentBreadcrumb extends HTMLElement {
constructor() {
super();
if (new window.URL(location.href).searchParams.get("nav") === "false") {
if (new URL(location.href).searchParams.get("nav") === "false") {
this.disabled = true;
return;
}
@ -65,15 +65,15 @@ class ComponentBreadcrumb extends window.HTMLElement {
const tasks = [];
for (let i=0; i<nToAnimate; i++) {
const n = previousChunks.length - i - 1;
const $chunk = assert.type(this.querySelector(`.component_path-element.n${n}`), window.HTMLElement);
const $chunk = assert.type(this.querySelector(`.component_path-element.n${n}`), HTMLElement);
tasks.push(animate($chunk, { time: 100, keyframes: slideYOut(-10) }));
}
await Promise.all(tasks);
}
// STEP2: setup the actual content
assert.type(this.querySelector(`[data-bind="path"]`), window.HTMLElement).innerHTML = pathChunks.map((chunk, idx) => {
const label = idx === 0 ? (window.CONFIG.name || "Filestash") : chunk;
assert.type(this.querySelector(`[data-bind="path"]`), 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) => {
if (highlight === true && word.length > 30) {
@ -130,7 +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 = assert.type(this.querySelector(`.component_path-element.n${n}`), window.HTMLElement);
const $chunk = assert.type(this.querySelector(`.component_path-element.n${n}`), HTMLElement);
await animate($chunk, { time: 100, keyframes: slideYIn(-5) });
}
}
@ -140,8 +140,8 @@ class ComponentBreadcrumb extends window.HTMLElement {
let state = this.hasAttribute("indicator");
if (state && this.getAttribute("indicator") !== "false") state = true;
let $indicator = assert.type(this.querySelector(`[data-bind="path"]`), window.HTMLElement);
$indicator = assert.type($indicator.lastChild.querySelector("span"), window.HTMLElement);
let $indicator = assert.type(this.querySelector(`[data-bind="path"]`), HTMLElement);
$indicator = assert.type($indicator.lastChild.querySelector("span"), HTMLElement);
if (state) {
$indicator.style.opacity = 1;
@ -162,8 +162,9 @@ class ComponentBreadcrumb extends window.HTMLElement {
}
setupDragDropTarget() {
this.querySelectorAll("a.label").forEach(($folder) => {
const $path = $folder.closest(".component_path-element");
this.querySelectorAll("a.label").forEach(($elmnt) => {
const $folder = assert.type($elmnt, HTMLElement);
const $path = assert.truthy($folder.closest(".component_path-element"));
$folder.ondrop = async(e) => {
$path.classList.remove("highlight");
const from = e.dataTransfer.getData("path");

View file

@ -45,7 +45,7 @@ export default function(ctrl) {
effect(rxjs.fromEvent(window, "keydown").pipe(
rxjs.filter((e) => regexStartFiles.test(fromHref(location.pathname)) &&
e.keyCode === 8 &&
assert.type(document.activeElement, window.HTMLElement).nodeName !== "INPUT"), // backspace in filemanager
assert.type(document.activeElement, HTMLElement).nodeName !== "INPUT"), // backspace in filemanager
rxjs.tap(() => {
const p = location.pathname.replace(new RegExp("/$"), "").split("/");
p.pop();

View file

@ -3,7 +3,7 @@ import { animate, slideYIn } from "../lib/animate.js";
import assert from "../lib/assert.js";
import { loadCSS } from "../helpers/loader.js";
export default class ComponentDropdown extends HTMLDivElement {
export default class ComponentDropdown extends HTMLElement {
constructor() {
super();
this.render();
@ -78,9 +78,9 @@ export default class ComponentDropdown extends HTMLDivElement {
`));
const setActive = () => this.classList.toggle("active");
assert.type(this.querySelector(".dropdown_button"), window.HTMLElement).onclick = () => {
assert.type(this.querySelector(".dropdown_button"), HTMLElement).onclick = () => {
setActive();
animate(assert.type(this.querySelector(".dropdown_container"), window.HTMLElement), {
animate(assert.type(this.querySelector(".dropdown_container"), HTMLElement), {
time: 100,
keyframes: slideYIn(2),
});
@ -88,4 +88,4 @@ export default class ComponentDropdown extends HTMLDivElement {
}
}
customElements.define("component-dropdown", ComponentDropdown, { extends: "div" });
customElements.define("component-dropdown", ComponentDropdown);

View file

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

View file

@ -78,7 +78,7 @@ export function $renderInput(options = {}) {
class="component_input"
/>
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
@ -135,7 +135,7 @@ export function $renderInput(options = {}) {
class="component_input"
/>
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
return $input;
@ -151,14 +151,14 @@ export function $renderInput(options = {}) {
</div>
`);
const $input = $div.querySelector("input");
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
const $icon = $div.querySelector("component-icon");
if ($icon instanceof window.HTMLElement) {
if ($icon instanceof HTMLElement) {
$icon.onclick = function(e) {
if (!(e.target instanceof window.HTMLElement)) return;
if (!(e.target instanceof HTMLElement)) return;
const $input = e.target?.parentElement?.previousElementSibling;
if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if ($input.getAttribute("type") === "password") $input.setAttribute("type", "text");
@ -174,7 +174,7 @@ export function $renderInput(options = {}) {
rows="8"
></textarea>
`);
if (!($textarea instanceof window.HTMLTextAreaElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($textarea instanceof HTMLTextAreaElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $textarea.value = value;
attrs.map((setAttribute) => setAttribute($textarea));
return $textarea;
@ -187,7 +187,7 @@ export function $renderInput(options = {}) {
readonly
/>
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
return $input;
@ -196,7 +196,7 @@ export function $renderInput(options = {}) {
const $input = createElement(`
<input type="hidden" />
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
$input.setAttribute("name", path.join("."));
return $input;
@ -219,7 +219,7 @@ export function $renderInput(options = {}) {
const $select = createElement(`
<select class="component_select"></select>
`);
if (!($select instanceof window.HTMLSelectElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($select instanceof HTMLSelectElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $select.value = value || props.default;
attrs.map((setAttribute) => setAttribute($select));
(options || []).forEach((name) => {
@ -242,7 +242,7 @@ export function $renderInput(options = {}) {
class="component_input"
/>
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
return $input;
@ -254,7 +254,7 @@ export function $renderInput(options = {}) {
class="component_input"
/>
`);
if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input");
else if (value) $input.value = value;
attrs.map((setAttribute) => setAttribute($input));
return $input;

View file

@ -1,4 +1,4 @@
class Icon extends window.HTMLElement {
class Icon extends HTMLElement {
static get observedAttributes() {
return ["name"];
}

View file

@ -2,10 +2,10 @@ import { createElement, onDestroy } from "../lib/skeleton/index.js";
import rxjs from "../lib/rx.js";
import { animate, opacityIn } from "../lib/animate.js";
class Loader extends window.HTMLElement {
class Loader extends HTMLElement {
constructor() {
super();
this.timeout = window.setTimeout(() => {
this.timeout = setTimeout(() => {
this.innerHTML = this.render({
inline: this.hasAttribute("inlined"),
});
@ -13,7 +13,7 @@ class Loader extends window.HTMLElement {
}
disconnectedCallback() {
window.clearTimeout(this.timeout);
clearTimeout(this.timeout);
}
render({ inline }) {
@ -61,7 +61,7 @@ export function createLoader($parent, opts = {}) {
<component-icon name="loading"></component-icon>
</div>
`);
const id = window.setTimeout(() => {
const id = setTimeout(() => {
append($icon);
animate($icon, { time: 750, keyframes: opacityIn() });
}, wait);

View file

@ -6,7 +6,7 @@ import { qs, qsa } from "../lib/dom.js";
import { loadCSS } from "../helpers/loader.js";
export function createModal(opts) {
const $dom = assert.type(qs(document.body, "component-modal"), window.HTMLElement);
const $dom = assert.type(qs(document.body, "component-modal"), HTMLElement);
assert.type($dom, ModalComponent);
return ($node, fn) => $dom.trigger($node, { onQuit: fn, ...opts });
@ -32,7 +32,7 @@ const $modal = createElement(`
</div>
`);
class ModalComponent extends window.HTMLElement {
class ModalComponent extends HTMLElement {
async connectedCallback() {
await loadCSS(import.meta.url, "./modal.css");
}
@ -123,7 +123,7 @@ class ModalComponent extends window.HTMLElement {
let size = targetHeight;
if (size === null) {
const $box = document.querySelector("#modal-box > div");
if ($box instanceof window.HTMLElement) size = $box.offsetHeight;
if ($box instanceof HTMLElement) size = $box.offsetHeight;
}
size = Math.round((document.body.offsetHeight - size) / 2);
if (size < 0) return 0;

View file

@ -16,7 +16,7 @@ const createNotification = async(msg, type) => createElement(`
</span>
`);
class NotificationComponent extends window.HTMLElement {
class NotificationComponent extends HTMLElement {
buffer = [];
async connectedCallback() {
@ -28,8 +28,8 @@ class NotificationComponent extends window.HTMLElement {
this.buffer.push({ message, type });
if (this.buffer.length !== 1) {
const $close = this.querySelector(".close");
if (!($close instanceof window.HTMLElement) || !$close.onclick) return;
$close.onclick(new window.MouseEvent("mousedown"));
if (!($close instanceof HTMLElement) || !$close.onclick) return;
$close.onclick(new MouseEvent("mousedown"));
return;
}
await this.run();
@ -46,16 +46,16 @@ class NotificationComponent extends window.HTMLElement {
});
const ids = [];
await Promise.race([
new Promise((done) => ids.push(window.setTimeout(() => {
done(new window.MouseEvent("mousedown"));
new Promise((done) => ids.push(setTimeout(() => {
done(new MouseEvent("mousedown"));
}, this.buffer.length === 1 ? 8000 : 800))),
new Promise((done) => ids.push(window.setTimeout(() => {
new Promise((done) => ids.push(setTimeout(() => {
const $close = $notification.querySelector(".close");
if (!($close instanceof window.HTMLElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: notification close button missing");
if (!($close instanceof HTMLElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: notification close button missing");
$close.onclick = done;
}, 1000))),
]);
ids.forEach((id) => window.clearTimeout(id));
ids.forEach((id) => clearTimeout(id));
await animate($notification, {
keyframes: slideYOut(10),
time: 200,

View file

@ -1 +1 @@
export function report(msg: Event|string, error?: Error, link?: string, lineNo?: number, columnno?: number);
export function report(msg: Event|string, err?: any, link?: string, lineNo?: number, columnno?: number);

View file

@ -1,4 +1,4 @@
export function report(msg, error, link, lineNo, columnNo) {
export function report(msg, err, link, lineNo, columnNo) {
if (window.navigator.onLine === false) return Promise.resolve();
let url = "./report?";
url += "url=" + encodeURIComponent(location.href) + "&";
@ -6,7 +6,7 @@ export function report(msg, error, link, lineNo, columnNo) {
url += "from=" + encodeURIComponent(link) + "&";
url += "from.lineNo=" + lineNo + "&";
url += "from.columnNo=" + columnNo;
if (error) url += "error=" + encodeURIComponent(error.message) + "&";
if (err instanceof Error) url += "error=" + encodeURIComponent(err.message) + "&";
return fetch(url, { method: "post" }).catch(() => {});
}

View file

@ -23,7 +23,7 @@ export function animate($node: HTMLElement | null, opts: {
fill?: string;
onExit?: () => void;
onEnter?: () => void;
}): Promise<void>;
}): Promise<() => void>;
export function slideXIn(dist: number): AnimationFrames[];

View file

@ -1,14 +1,32 @@
export default class assert {
/**
* @param {*} object
* @param {Function} type
* @param {string} [msg]
* @return {*}
* @throws {TypeError}
*/
static type(object, type, msg) {
if (!(object instanceof type)) throw new TypeError(msg || `assertion failed - unexpected type for ${object.toString()}`);
return object;
}
/**
* @param {*} object
* @param {string} [msg]
* @return {*}
* @throws {TypeError}
*/
static truthy(object, msg) {
if (!object) throw new TypeError(msg || `assertion failed - object is not truthy`);
return object;
}
static fail(object, msg) {
throw new TypeError(msg || `assertion failed - ${object}`);
/**
* @param {string} msg
* @throws {TypeError}
*/
static fail(msg) {
throw new TypeError(msg);
}
}

View file

@ -11,7 +11,7 @@ export function join(baseURL, segment) {
}
export function forwardURLParams(url, allowed = []) {
const _url = new URL(location.origin + "/" + url);
const _url = new URL(window.location.origin + "/" + url);
for (const [key, value] of new URLSearchParams(location.search)) {
if (allowed.indexOf(key) < 0) continue;
_url.searchParams.set(key, value);

View file

@ -3,9 +3,9 @@ DocumentFragment.prototype.replaceChildren = replaceChildren;
Element.prototype.replaceChildren = replaceChildren;
function replaceChildren(...new_children) {
const { childNodes } = this;
while (childNodes.length) {
childNodes[0].remove();
}
this.append(...new_children);
const { childNodes } = this;
while (childNodes.length) {
childNodes[0].remove();
}
this.append(...new_children);
}

View file

@ -1,7 +1,7 @@
import type { Observer } from "rx-core";
import type { Observer, Observable as coreObservable } from "rx-core";
import {
Observable, fromEvent, startWith,
fromEvent, startWith, Observable,
catchError, tap, first, of,
map, mapTo, filter, mergeMap, EMPTY, empty,
switchMapTo, switchMap,
@ -74,6 +74,6 @@ export function stateMutation($node: HTMLElement, attr: string);
export function preventDefault(): typeof tap;
export function onClick($node: HTMLElement): typeof fromEvent;
export function onClick($node: HTMLElement): ReturnType<typeof fromEvent>;
export function onLoad($node: HTMLElement): typeof fromEvent;
export function onLoad($node: HTMLElement): void;

View file

@ -19,19 +19,19 @@ const getFn = (obj, arg0, ...args) => {
};
export function applyMutation($node, ...keys) {
assert.type($node, window.HTMLElement);
assert.type($node, HTMLElement);
const execute = getFn($node, ...keys);
return rxjs.tap((val) => Array.isArray(val) ? execute(...val) : execute(val));
}
export function applyMutations($node, ...keys) {
assert.type($node, window.HTMLElement);
assert.type($node, HTMLElement);
const execute = getFn($node, ...keys);
return rxjs.tap((vals) => vals.forEach((val) => execute(val)));
}
export function stateMutation($node, attr) {
assert.type($node, window.HTMLElement);
assert.type($node, HTMLElement);
return rxjs.tap((val) => $node[attr] = val);
}
@ -41,19 +41,19 @@ export function preventDefault() {
export function onClick($node) {
const sideE = ($node) => {
assert.type($node, window.HTMLElement);
assert.type($node, HTMLElement);
return rxjs.fromEvent($node, "click").pipe(
rxjs.map(() => $node)
);
};
if ($node instanceof window.NodeList) return rxjs.merge(
if ($node instanceof NodeList) return rxjs.merge(
...[...$node].map(($n) => sideE($n)),
);
return sideE($node);
}
export function onLoad($node) {
assert.type($node, window.HTMLElement);
assert.type($node, HTMLElement);
return new rxjs.Observable((observer) => {
$node.onload = () => {
observer.next($node);

View file

@ -59,7 +59,7 @@ async function load(route, opts) {
export function createElement(str) {
const $n = window.document.createElement("div");
$n.innerHTML = str;
if (!($n.firstElementChild instanceof window.HTMLElement)) throw new Error("createElement - unexpected type");
if (!($n.firstElementChild instanceof HTMLElement)) throw new Error("createElement - unexpected type");
return $n.firstElementChild;
}
@ -73,10 +73,10 @@ export function createFragment(str) {
}
export function createRender($parent) {
if (!($parent instanceof window.HTMLElement)) throw new Error(`assert failed: createRender on non HTMLElement`);
if (!($parent instanceof HTMLElement)) throw new Error(`assert failed: createRender on non HTMLElement`);
return ($view) => {
if ($view instanceof window.HTMLElement) $parent.replaceChildren($view);
else if ($view instanceof window.DocumentFragment) $parent.replaceChildren($view);
if ($view instanceof HTMLElement) $parent.replaceChildren($view);
else if ($view instanceof DocumentFragment) $parent.replaceChildren($view);
else throw new Error(`Unknown view type: ${typeof $view}`);
return $parent;
};

View file

@ -1,8 +1,8 @@
const triggerPageChange = () => window.dispatchEvent(new window.Event("pagechange"));
const trimPrefix = (value, prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
const trimPrefix = (value = "", prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
const _base = window.document.head.querySelector("base").getAttribute("href").replace(new RegExp("/$"), "");
export const base = () => _base;
const _base = window.document.head.querySelector("base")?.getAttribute("href")?.replace(new RegExp("/$"), "");
export const base = () => _base || "";
export const fromHref = (href) => trimPrefix(href, base());
export const toHref = (href) => base() + href;

View file

@ -8,6 +8,7 @@
"A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Un fichier nommé \"{{VALUE}}\" a été créé",
"A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Un dossier nommé \"{{VALUE}}\" a été créé",
"BEAUTIFUL_URL": "id_du_lien",
"BOOKMARK": "favoris",
"CAMERA": "appareil",
"CANCEL": "annuler",
"CANNOT_ESTABLISH_A_CONNECTION": "Impossible d'établir une connexion",
@ -20,6 +21,7 @@
"CONNECT": "connexion",
"CONNECTION_LOST": "Connection perdue",
"COPIED_TO_CLIPBOARD": "Copié dans le presse-papier",
"CREATE_A_TAG": "créer un tag",
"CREATE_A_NEW_LINK": "créer un lien partagé",
"CURRENT": "en cours",
"CURRENT_UPLOAD": "en cours",

View file

@ -17,7 +17,7 @@ function mainReorderKey(argv) {
function mainAddTranslationKey(argv) {
const key = argv[3];
const filepath = argv[2];
if (!filepath) throw new Error("missing args")
if (!filepath) throw new Error("missing args");
else if (!key) return;
const json = JSON.parse(fs.readFileSync(filepath));
@ -28,6 +28,6 @@ function mainAddTranslationKey(argv) {
// usage: find *.json -type f -exec node script.js {} \;
(function() {
mainAddTranslationKey(process.argv)
mainReorderKey(process.argv)
})()
mainAddTranslationKey(process.argv);
mainReorderKey(process.argv);
})();

View file

@ -1,5 +1,5 @@
export async function init() {
if (!window.CONFIG.enable_chromecast) {
if (!window.CONFIG["enable_chromecast"]) {
return Promise.resolve();
} else if (!("chrome" in window)) {
return Promise.resolve();

View file

@ -1,6 +1,6 @@
import { ApplicationError } from "../../lib/error.js";
class BoxItem extends window.HTMLDivElement {
class BoxItem extends HTMLElement {
constructor() {
super();
this.attributeChangedCallback();
@ -42,4 +42,4 @@ class BoxItem extends window.HTMLDivElement {
}
}
customElements.define("box-item", BoxItem, { extends: "div" });
customElements.define("box-item", BoxItem);

View file

@ -36,7 +36,7 @@ export default async function(render) {
const init$ = getMiddlewareAvailable().pipe(
rxjs.first(),
rxjs.map((specs) => Object.keys(specs).map((label) => createElement(`
<div is="box-item" data-label="${label}"></div>
<box-item data-label="${label}"></box-item>
`))),
rxjs.tap(() => {
qs($page, "h2").classList.remove("hidden");

View file

@ -101,7 +101,7 @@ export function getState() {
if (!authType) return config;
const $formIDP = document.querySelector(`[data-bind="idp"]`);
if (!($formIDP instanceof window.HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: idp isn't a form");
if (!($formIDP instanceof HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: idp isn't a form");
let formValues = [...new FormData($formIDP)];
config.middleware.identity_provider = {
type: authType,
@ -121,7 +121,7 @@ export function getState() {
};
const $formAM = document.querySelector(`[data-bind="attribute-mapping"]`);
if (!($formAM instanceof window.HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: attribute mapping isn't a form");
if (!($formAM instanceof HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: attribute mapping isn't a form");
formValues = [...new FormData($formAM)];
config.middleware.attribute_mapping = {
related_backend: (formValues.shift() || [])[1],

View file

@ -193,7 +193,6 @@ function componentStep2(render) {
rxjs.filter((config) => config["log"]["telemetry"] !== true),
rxjs.mergeMap(async(config) => {
const enabled = await componentTelemetryPopup(createModal({ withButtonsRight: "OK" }));
console.log(enabled);
if (enabled === false) return null;
config["log"]["telemetry"] = enabled;
return config;

View file

@ -4,6 +4,7 @@ import rxjs, { effect, applyMutation, applyMutations, preventDefault, onClick }
import ajax from "../../lib/ajax.js";
import { qs, qsa, safe } from "../../lib/dom.js";
import { animate, slideYIn, transition, opacityIn } from "../../lib/animate.js";
import assert from "../../lib/assert.js";
import { createForm } from "../../lib/form.js";
import { settings_get, settings_put } from "../../lib/settings.js";
import t from "../../locales/index.js";
@ -128,7 +129,7 @@ export default async function(render) {
const toggleLoader = (hide) => {
if (hide) {
$page.classList.add("hidden");
$page.parentElement.appendChild($loader);
assert.truthy($page.parentElement).appendChild($loader);
} else {
$loader.remove();
$page.classList.remove("hidden");
@ -216,7 +217,7 @@ export default async function(render) {
// feature7: empty connection handling
effect(connections$.pipe(
rxjs.filter((conns) => conns.length === 0),
rxjs.mergeMap((a) => Promise.reject(new Error("no backend selected"))),
rxjs.mergeMap(() => Promise.reject(new Error("there is nothing here"))), // TODO: check translation?
rxjs.catchError(ctrlError()),
));
}

View file

@ -18,7 +18,7 @@ export default function(render = createRender(qs(document.body, "[role=\"main\"]
return function(err) {
const [msg, trace] = processError(err);
const link = forwardURLParams(calculateBacklink(fromHref(location.pathname)), ["share"]);
const link = forwardURLParams(calculateBacklink(fromHref(window.location.pathname)), ["share"]);
const $page = createElement(`
<div>
<style>${css}</style>
@ -32,7 +32,7 @@ export default function(render = createRender(qs(document.body, "[role=\"main\"]
<h2>${t(msg)}</h2>
<p>
<button class="light" data-bind="details">${t("More details")}</button>
<button class="primary" data-bind="refresh">${t("Refresh")}</button>
<button class="primary" data-bind="refresh">${t("Reload")}</button>
<pre class="hidden"><code>${strToHTML(trace)}</code></pre>
</p>
</div>

View file

@ -25,8 +25,8 @@ export default function(render) {
else if (step === "email") return ctrlEmail(render, { shareID, setState });
else if (step === "code") return ctrlEmailCodeVerification(render, { shareID, setState });
else if (step === "done") {
if (isDir(state.path)) navigate(toHref(`/files/?share=${shareID}`));
else navigate(toHref(`/view/${basename(state.path)}?share=${shareID}&nav=false`));
if (isDir(state["path"])) navigate(toHref(`/files/?share=${shareID}`));
else navigate(toHref(`/view/${basename(state["path"])}?share=${shareID}&nav=false`));
return rxjs.EMPTY;
}
else assert.fail(`unknown step: "${step}"`);

View file

@ -2,6 +2,7 @@ import { createElement, createRender } from "../lib/skeleton/index.js";
import rxjs, { effect } from "../lib/rx.js";
import { ApplicationError } from "../lib/error.js";
import { basename } from "../lib/path.js";
import assert from "../lib/assert.js";
import { loadCSS } from "../helpers/loader.js";
import WithShell, { init as initShell } from "../components/decorator_shell_filemanager.js";
import { init as initMenubar } from "./viewerpage/component_menubar.js";
@ -46,7 +47,7 @@ export default WithShell(async function(render) {
render($page);
// feature: render viewer application
effect(rxjs.of(window.CONFIG.mime || {}).pipe(
effect(rxjs.of(window.CONFIG["mime"] || {}).pipe(
rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
rxjs.mergeMap(([opener, opts]) => rxjs.from(loadModule(opener)).pipe(rxjs.tap((module) => {
module.default(createRender($page), { ...opts, acl$: options() });
@ -58,8 +59,9 @@ export default WithShell(async function(render) {
effect(rxjs.of(new URL(location.toString()).searchParams.get("nav")).pipe(
rxjs.filter((value) => value === "false"),
rxjs.tap(() => {
$page.parentElement.style.border = "none";
$page.parentElement.style.borderRadius = "0";
const $parent = assert.truthy($page.parentElement);
$parent.style.border = "none";
$parent.style.borderRadius = "0";
}),
));
});
@ -68,7 +70,7 @@ export async function init() {
return Promise.all([
loadCSS(import.meta.url, "./ctrl_viewerpage.css"),
initShell(), initMenubar(), initCache(),
rxjs.of(window.CONFIG.mime || {}).pipe(
rxjs.of(window.CONFIG["mime"] || {}).pipe(
rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
rxjs.mergeMap(([opener]) => loadModule(opener)),
rxjs.mergeMap((module) => typeof module.init === "function"? module.init() : rxjs.EMPTY),

View file

@ -1,17 +1,38 @@
import assert from "../../lib/assert.js";
import { getSession } from "../../model/session.js";
class ICache {
async get() { throw new Error("NOT_IMPLEMENTED"); }
/**
* @param {string} _path
* @return {Promise<any>}
*/
async get(_path) { throw new Error("NOT_IMPLEMENTED"); }
async store() { throw new Error("NOT_IMPLEMENTED"); }
/**
* @param {string} _path
* @param {any} _data
* @return {Promise<void>}
*/
async store(_path, _data) { throw new Error("NOT_IMPLEMENTED"); }
/**
* @return {Promise<void>}
*/
async remove() { throw new Error("NOT_IMPLEMENTED"); }
/**
* @param {string} path
* @param {function(any): any} fn
* @return {Promise<void>}
*/
async update(path, fn) {
const data = await this.get(path);
return this.store(path, fn(data || {}));
}
/**
* @return {Promise<void>}
*/
async destroy() { throw new Error("NOT_IMPLEMENTED"); }
}
@ -21,14 +42,23 @@ class InMemoryCache extends ICache {
this.data = {};
}
/**
* @override
*/
async get(path) {
return this.data[this._key(path)] || null;
}
/**
* @override
*/
async store(path, obj) {
this.data[this._key(path)] = obj;
}
/**
* @override
*/
async remove(path, exact = true) {
if (!path) {
this.data = {};
@ -46,6 +76,9 @@ class InMemoryCache extends ICache {
}
}
/**
* @override
*/
async destroy() {
this.data = {};
}
@ -58,7 +91,7 @@ class InMemoryCache extends ICache {
class IndexDBCache extends ICache {
DB_VERSION = 5;
FILE_PATH = "file_path";
db = null;
/** @type {Promise<IDBDatabase> | null} */ db = null;
constructor() {
super();
@ -68,25 +101,31 @@ class IndexDBCache extends ICache {
this.db = new Promise((done, err) => {
request.onsuccess = (e) => {
done(e.target.result);
done(assert.truthy(e.target).result);
};
request.onerror = () => err(new Error("INDEXEDDB_NOT_SUPPORTED"));
});
}
/**
* @override
*/
async get(path) {
const db = await this.db;
const db = assert.truthy(await this.db);
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) => {
query.onsuccess = (e) => done(query.result || null);
query.onsuccess = () => done(query.result || null);
query.onerror = () => done(null);
});
}
/**
* @override
*/
async store(path, value = {}) {
const db = await this.db;
const db = assert.truthy(await this.db);
const tx = db.transaction(this.FILE_PATH, "readwrite");
const store = tx.objectStore(this.FILE_PATH);
@ -103,8 +142,11 @@ class IndexDBCache extends ICache {
});
}
/**
* @override
*/
async remove(path, exact = true) {
const db = await this.db;
const db = assert.truthy(await this.db);
const tx = db.transaction(this.FILE_PATH, "readwrite");
const store = tx.objectStore(this.FILE_PATH);
const key = this._key(path);
@ -179,7 +221,7 @@ export async function init() {
cache = new InMemoryCache();
if (!("indexedDB" in window)) return;
cache = new IndexDBCache();
cache = assert.truthy(new IndexDBCache());
return cache.db.catch((err) => {
if (err.message === "INDEXEDDB_NOT_SUPPORTED") {
// Firefox in private mode act like if it supports indexedDB but

View file

@ -77,7 +77,7 @@ export default async function(render) {
)),
rxjs.mergeMap(({ show_hidden, files, ...rest }) => {
if (show_hidden === false) files = files.filter(({ name }) => name[0] !== ".");
files = sort(files, rest.sort, rest.order);
files = sort(files, rest["sort"], rest["order"]);
return rxjs.of({ ...rest, files });
}),
rxjs.map((data) => ({ ...data, count: count++ })),
@ -265,7 +265,7 @@ export default async function(render) {
rxjs.filter((e) => e.key === "a" &&
(e.ctrlKey || e.metaKey) &&
(files$.value || []).length > 0 &&
assert.type(document.activeElement, window.HTMLElement).tagName !== "INPUT"),
assert.type(document.activeElement, HTMLElement).tagName !== "INPUT"),
preventDefault(),
rxjs.tap(() => {
clearSelection();
@ -289,10 +289,10 @@ export default async function(render) {
));
effect(getSelection$().pipe(rxjs.tap(() => {
for (const $thing of $page.querySelectorAll(".component_thing")) {
const checked = isSelected(parseInt($thing.getAttribute("data-n")));
const checked = isSelected(parseInt(assert.truthy($thing.getAttribute("data-n"))));
$thing.classList.add(checked ? "selected" : "not-selected");
$thing.classList.remove(checked ? "not-selected" : "selected");
qs($thing, `input[type="checkbox"]`).checked = checked;
qs(assert.type($thing, HTMLElement), `input[type="checkbox"]`).checked = checked;
};
})));

View file

@ -1,4 +1,4 @@
import { createElement } from "../../lib/skeleton/index.js";
import { createElement, nop } from "../../lib/skeleton/index.js";
import rxjs, { effect, onClick, preventDefault } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { animate } from "../../lib/animate.js";
@ -71,7 +71,7 @@ export default async function(render) {
$icon.setAttribute("alt", alt);
$input.value = "";
$input.nextSibling.setAttribute("name", alt);
let done = Promise.resolve();
let done = Promise.resolve(nop);
if ($node.classList.contains("hidden")) done = animate($node, {
keyframes: [{ height: `0px` }, { height: "50px" }],
time: 100,

View file

@ -2,6 +2,7 @@ import { createElement, createRender, createFragment, onDestroy } from "../../li
import rxjs, { effect, onClick, preventDefault } from "../../lib/rx.js";
import { animate, slideXIn, slideYIn } from "../../lib/animate.js";
import { loadCSS } from "../../helpers/loader.js";
import assert from "../../lib/assert.js";
import { qs, qsa } from "../../lib/dom.js";
import { basename } from "../../lib/path.js";
import t from "../../locales/index.js";
@ -51,7 +52,7 @@ export default async function(render) {
render($page);
onDestroy(() => clearSelection());
const $scroll = $page.closest(".scroll-y");
const $scroll = assert.type($page.closest(".scroll-y"), HTMLElement);
componentLeft(createRender(qs($page, ".action.left")), { $scroll });
componentRight(createRender(qs($page, ".action.right")));
@ -112,13 +113,13 @@ function componentLeft(render, { $scroll }) {
<button data-action="rename" title="${t("Rename")}"${toggleDependingOnPermission(currentPath(), "rename")}>
${t("Rename")}
</button>
<button data-action="share" title="${t("Share")}" class="${(window.CONFIG.enable_share && !new URLSearchParams(location.search).has("share")) ? "" : "hidden"}">
<button data-action="share" title="${t("Share")}" class="${(window.CONFIG["enable_share"] && !new URLSearchParams(location.search).has("share")) ? "" : "hidden"}">
${t("Share")}
</button>
<button data-action="embed" class="hidden" title="${t("Embed")}">
<button data-action="embed" title="${t("Embed")}">
${t("Embed")}
</button>
<button data-action="tag" class="hidden" title="${t("Tag")}">
<button data-action="tag" title="${t("Tag")}">
${t("Tag")}
</button>
`))),
@ -396,6 +397,7 @@ export function init() {
loadCSS(import.meta.url, "../../css/designsystem_dropdown.css"),
loadCSS(import.meta.url, "./ctrl_submenu.css"),
loadCSS(import.meta.url, "./modal_share.css"),
loadCSS(import.meta.url, "./modal_tag.css"),
]);
}
@ -407,10 +409,10 @@ function generateLinkAttributes(selections) {
const regDir = new RegExp("/$");
const isDir = regDir.test(path);
if (isDir) {
filename = basename(path.replace(regDir, "")) + ".zip"
filename = basename(path.replace(regDir, "")) + ".zip";
} else {
filename = basename(path);
href = "api/files/cat?"
href = "api/files/cat?";
}
}
href += selections.map(({ path }) => "path=" + encodeURIComponent(path)).join("&");

View file

@ -27,8 +27,8 @@ export default async function(render) {
<div is="component_filezone"></div>
<div is="component_upload_fab"></div>
`);
componentFilezone(createRender(assert.type($page.children[0], window.HTMLElement)), { workers$ });
componentUploadFAB(createRender(assert.type($page.children[1], window.HTMLElement)), { workers$ });
componentFilezone(createRender(assert.type($page.children[0], HTMLElement)), { workers$ });
componentUploadFAB(createRender(assert.type($page.children[1], HTMLElement)), { workers$ });
render($page);
}),
));
@ -62,7 +62,7 @@ function componentUploadFAB(render, { workers$ }) {
function componentFilezone(render, { workers$ }) {
const selector = `[data-bind="filemanager-children"]`;
const $target = assert.type(document.body.querySelector(selector), window.HTMLElement);
const $target = assert.type(qs(document.body, selector), HTMLElement);
$target.ondragenter = (e) => {
if (!isNativeFileUpload(e)) return;
@ -78,7 +78,7 @@ function componentFilezone(render, { workers$ }) {
} else if (e.dataTransfer.files instanceof window.FileList) {
workers$.next(await processFiles(e.dataTransfer.files));
} else {
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js", e.dataTransfer);
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js");
}
clearTimeout(loadID);
render(createFragment(""));
@ -134,15 +134,13 @@ function componentUploadQueue(render, { workers$ }) {
};
// feature1: close the queue
onClick(qs($page, `img[alt="close"]`)).pipe(
rxjs.tap(async(cancel) => {
const cleanup = await animate($page, { time: 200, keyframes: slideYOut(50) });
$content.innerHTML = "";
$page.classList.add("hidden");
updateTotal.reset();
cleanup();
}),
).subscribe();
onClick(qs($page, `img[alt="close"]`)).pipe(rxjs.tap(async() => {
const cleanup = await animate($page, { time: 200, keyframes: slideYOut(50) });
$content.innerHTML = "";
$page.classList.add("hidden");
updateTotal.reset();
cleanup();
})).subscribe();
// feature2: setup the task queue in the dom
workers$.subscribe(({ tasks }) => {
@ -150,7 +148,7 @@ function componentUploadQueue(render, { workers$ }) {
updateTotal.addToTotal(tasks.length);
const $fragment = document.createDocumentFragment();
for (let i = 0; i<tasks.length; i++) {
const $task = $file.cloneNode(true);
const $task = assert.type($file.cloneNode(true), HTMLElement);
$fragment.appendChild($task);
$task.setAttribute("data-path", tasks[i]["path"]);
$task.firstElementChild.firstElementChild.textContent = tasks[i]["path"]; // qs($todo, ".file_path span.path")
@ -176,10 +174,10 @@ function componentUploadQueue(render, { workers$ }) {
let last = 0;
return (nworker, currentWorkerSpeed) => {
workersSpeed[nworker] = currentWorkerSpeed;
if (new Date() - last <= 500) return;
last = new Date();
if (new Date().getTime() - last <= 500) return;
last = new Date().getTime();
const speed = workersSpeed.reduce((acc, el) => acc + el, 0);
const $speed = assert.type($page.firstElementChild.nextElementSibling.firstElementChild, window.HTMLElement);
const $speed = assert.type($page.firstElementChild?.nextElementSibling?.firstElementChild, HTMLElement);
$speed.textContent = formatSpeed(speed);
};
}(new Array(MAX_WORKERS).fill(0)));
@ -208,7 +206,7 @@ function componentUploadQueue(render, { workers$ }) {
$close.removeEventListener("click", cancel);
break;
case "error":
const $retry = assert.type($iconRetry.cloneNode(true), window.HTMLElement);
const $retry = assert.type($iconRetry.cloneNode(true), HTMLElement);
updateDOMGlobalTitle($page, t("Error"));
updateDOMGlobalSpeed(nworker, 0);
updateDOMTaskProgress($task, t("Error"));
@ -276,7 +274,7 @@ function componentUploadQueue(render, { workers$ }) {
const nworker = reservations.indexOf(false);
if (nworker === -1) break; // the pool of workers is already to its max
reservations[nworker] = true;
noFailureAllowed(processWorkerQueue.bind(this, nworker)).then(() => reservations[nworker] = false);
noFailureAllowed(processWorkerQueue.bind(null, nworker)).then(() => reservations[nworker] = false);
}
});
}
@ -295,17 +293,24 @@ function workerImplFile({ error, progress, speed }) {
this.prevProgress = [];
}
/**
* @override
*/
cancel() {
this.xhr.abort();
assert.type(this.xhr, XMLHttpRequest).abort();
}
/**
* @override
*/
async run({ file, path, virtual }) {
const xhr = new XMLHttpRequest();
this.xhr = xhr;
return new Promise((resolve, reject) => {
this.xhr = new XMLHttpRequest();
this.xhr.open("POST", "api/files/cat?path=" + encodeURIComponent(path));
this.xhr.withCredentials = true;
this.xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
this.xhr.upload.onprogress = (e) => {
xhr.open("POST", "api/files/cat?path=" + encodeURIComponent(path));
xhr.withCredentials = true;
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
xhr.upload.onprogress = (e) => {
if (!e.lengthComputable) return;
const percent = Math.floor(100 * e.loaded / e.total);
progress(percent);
@ -329,26 +334,26 @@ function workerImplFile({ error, progress, speed }) {
this.prevProgress.shift();
}
};
this.xhr.upload.onabort = () => {
xhr.upload.onabort = () => {
reject(ABORT_ERROR);
error(ABORT_ERROR);
virtual.afterError();
};
this.xhr.onload = () => {
xhr.onload = () => {
progress(100);
if (this.xhr.status !== 200) {
if (xhr.status !== 200) {
virtual.afterError();
reject(new Error(this.xhr.statusText));
reject(new Error(xhr.statusText));
return;
}
virtual.afterSuccess();
resolve(null);
};
this.xhr.onerror = function(e) {
xhr.onerror = function(e) {
reject(new AjaxError("failed", e, "FAILED"));
virtual.afterError();
};
file().then((f) => this.xhr.send(f)).catch((err) => this.xhr.onerror(err));
file().then((f) => xhr.send(f)).catch((err) => xhr.onerror && xhr.onerror(err));
});
}
}();
@ -361,17 +366,24 @@ function workerImplDirectory({ error, progress }) {
this.xhr = null;
}
/**
* @override
*/
cancel() {
if (this.xhr instanceof XMLHttpRequest) this.xhr.abort();
assert.type(this.xhr, XMLHttpRequest).abort();
}
/**
* @override
*/
run({ virtual, path }) {
const xhr = new XMLHttpRequest();
this.xhr = xhr;
return new Promise((resolve, reject) => {
this.xhr = new XMLHttpRequest();
this.xhr.open("POST", "api/files/mkdir?path=" + encodeURIComponent(path));
this.xhr.withCredentials = true;
this.xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
this.xhr.onerror = function(e) {
xhr.open("POST", "api/files/mkdir?path=" + encodeURIComponent(path));
xhr.withCredentials = true;
xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest");
xhr.onerror = function(e) {
reject(new AjaxError("failed", e, "FAILED"));
};
@ -384,24 +396,24 @@ function workerImplDirectory({ error, progress }) {
}
progress(percent);
}, 100);
this.xhr.upload.onabort = () => {
xhr.upload.onabort = () => {
reject(ABORT_ERROR);
error(ABORT_ERROR);
clearInterval(id);
virtual.afterError();
};
this.xhr.onload = () => {
xhr.onload = () => {
clearInterval(id);
progress(100);
if (this.xhr.status !== 200) {
if (xhr.status !== 200) {
virtual.afterError();
err(new Error(this.xhr.statusText));
reject(new Error(xhr.statusText));
return;
}
virtual.afterSuccess();
resolve(null);
};
this.xhr.send(null);
xhr.send(null);
});
}
}();
@ -462,8 +474,9 @@ async function processFiles(filelist) {
};
break;
default:
assert.fail(type, `NOT_SUPPORTED type="${type}"`);
assert.fail(`NOT_SUPPORTED type="${type}"`);
}
task = assert.truthy(task);
task.virtual.before();
tasks.push(task);
}
@ -497,6 +510,7 @@ async function processItems(itemList) {
exec: workerImplFile,
virtual: save(path, entrySize),
done: false,
ready: () => false,
};
size += entrySize;
} else if (entry.isDirectory) {
@ -506,12 +520,13 @@ async function processItems(itemList) {
exec: workerImplDirectory,
virtual: mkdir(path),
done: false,
ready: () => false,
};
queue = queue.concat(await new Promise((resolve) => {
entry.createReader().readEntries(resolve);
}));
} else {
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js", entry);
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js");
}
task.ready = () => {
const isInDirectory = (filepath, folder) => folder.indexOf(filepath) === 0;

View file

@ -112,6 +112,4 @@ function _moveHiddenFilesDownward(fileA, fileB) {
return 0;
}
export const isNativeFileUpload = (e) => {
return JSON.stringify(e.dataTransfer.types.slice(-1)) === "[\"Files\"]";
}
export const isNativeFileUpload = (e) => JSON.stringify(e.dataTransfer.types.slice(-1)) === "[\"Files\"]";

View file

@ -36,7 +36,7 @@ function renderDesktop(render, removeLabel) {
ret.next();
ret.complete();
return ret.toPromise();
}).bind(this, MODAL_RIGHT_BUTTON);
}).bind(null, MODAL_RIGHT_BUTTON);
$input.focus();

View file

@ -33,7 +33,7 @@ function renderDesktop(render, filename) {
}
ret.next(value);
ret.complete();
}).bind(this, MODAL_RIGHT_BUTTON);
}).bind(null, MODAL_RIGHT_BUTTON);
const ext = extname(filename);
$input.value = filename;

View file

@ -36,9 +36,10 @@ export default function(render, { path }) {
render($modal);
const ret = new rxjs.Subject();
const role$ = new rxjs.BehaviorSubject(null);
const state = {
form: {},
links: null,
/** @type {object} */ form: {},
/** @type {any[] | null} */ links: null,
};
// feature: select
@ -84,7 +85,12 @@ export default function(render, { path }) {
body,
url: `api/share/${id}`,
}).toPromise();
state.links.push({ ...body, path: body.path.substring(currentPath().length - 1) });
;
// if (state.links === null) assert.fail("ttest");
assert.truthy(state.links).push({
...body,
path: body.path.substring(currentPath().length - 1),
});
role$.next(null);
},
remove: async({ id }) => {
@ -92,7 +98,7 @@ export default function(render, { path }) {
method: "DELETE",
url: `api/share/${id}`,
}).toPromise();
state.links = state.links.filter((link) => link.id !== id);
state.links = (state.links || []).filter((link) => link && link.id !== id);
role$.next(null);
},
all: async() => {
@ -240,7 +246,7 @@ async function ctrlCreateShare(render, { save, formState }) {
? t("Password")
: label === "url_enable"
? t("Custom Link url")
: assert.fail(label, "unknown label");
: assert.fail("unknown label");
return createElement(`
<div class="component_supercheckbox">
<label>
@ -273,7 +279,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(assert.type($form.closest(".component_share"), window.HTMLElement), `input[name="create"]`).value = `${location.origin}${toHref("/s/" + id)}`;
qs(assert.type($form.closest(".component_share"), HTMLElement), `input[name="create"]`).value = `${location.origin}${toHref("/s/" + id)}`;
})));
// feature: create a shared link
@ -281,7 +287,7 @@ async function ctrlCreateShare(render, { save, formState }) {
effect(onClick(qs($page, ".shared-link")).pipe(
rxjs.first(),
rxjs.switchMap(async() => {
const body = [...new FormData(assert.type(qs(document.body, ".component_share form"), window.HTMLFormElement))]
const body = [...new FormData(assert.type(qs(document.body, ".component_share form"), HTMLFormElement))]
.reduce((acc, [key, value]) => {
if (value && key.slice(-7) !== "_enable") acc[key] = value;
return acc;
@ -328,7 +334,7 @@ function shareObjToRole({ can_read, can_write, can_upload }) {
} else if (can_read === true && can_write === true && can_upload === true) {
return "editor";
}
return null;
return undefined;
}
export function copyToClipboard(str) {

View file

@ -0,0 +1,48 @@
.component_tag input {
font-size: 1.2em;
}
.component_tag input::placeholder {
font-weight: 100;
}
.component_tag .box {
display: flex;
background: var(--bg-color);
transition: background 0.1s ease;
padding: 5px 10px;
border-radius: 3px;
}
.component_tag .box.active {
background: var(--primary);
color: var(--color);
}
.component_tag .box > div {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.component_tag .box img {
width: 20px;
background: rgba(0, 0, 0, 0.05);
border-radius: 50%;
margin-left: 5px;
}
.component_tag .box img[alt="close"] {
width: 14px;
padding: 3px;
}
.component_tag .box .count {
opacity: 0.5;
font-size: 0.9rem;
}
.component_tag .box .count:before {
content: "[";
}
.component_tag .box .count:after {
content: "]";
}
.component_tag .scroll-y {
overflow-y: auto !important;
max-height: 200px;
}

View file

@ -1,9 +1,32 @@
import { createElement } from "../../lib/skeleton/index.js";
import t from "../../locales/index.js";
export default function(render) {
const $modal = createElement(`
<div>
TAG MODAL
<div class="component_tag">
<form>
<input class="component_input" type="text" placeholder="${t("Create a Tag")}" value="">
</form>
<div class="scroll-y">
<div class="box no-select">
<div>test <span class="count">2</span></div>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMwOSIgZD0ibSAxNi44ODUsMTUuNDU1IC00LjU5LC00LjU4IC00LjU5LDQuNTggLTEuNDEsLTEuNDEgNiwtNiA2LDYgeiIgLz4KICA8cGF0aCBmaWxsPSJub25lIiBkPSJNMC0uMjVoMjR2MjRIMHoiIC8+Cjwvc3ZnPgo=" alt="arrow_top">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMyMSIgZD0ibSA3LjcwNSw4LjA0NSA0LjU5LDQuNTggNC41OSwtNC41OCAxLjQxLDEuNDEgLTYsNiAtNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_bottom">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close">
</div>
<div class="box no-select">
<div>kjhjk <span class="count">1</span></div>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMwOSIgZD0ibSAxNi44ODUsMTUuNDU1IC00LjU5LC00LjU4IC00LjU5LDQuNTggLTEuNDEsLTEuNDEgNiwtNiA2LDYgeiIgLz4KICA8cGF0aCBmaWxsPSJub25lIiBkPSJNMC0uMjVoMjR2MjRIMHoiIC8+Cjwvc3ZnPgo=" alt="arrow_top">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMyMSIgZD0ibSA3LjcwNSw4LjA0NSA0LjU5LDQuNTggNC41OSwtNC41OCAxLjQxLDEuNDEgLTYsNiAtNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_bottom">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close">
</div>
<div class="box no-select active">
<div>jh <span class="count">2</span></div>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMwOSIgZD0ibSAxNi44ODUsMTUuNDU1IC00LjU5LC00LjU4IC00LjU5LDQuNTggLTEuNDEsLTEuNDEgNiwtNiA2LDYgeiIgLz4KICA8cGF0aCBmaWxsPSJub25lIiBkPSJNMC0uMjVoMjR2MjRIMHoiIC8+Cjwvc3ZnPgo=" alt="arrow_top">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMyMSIgZD0ibSA3LjcwNSw4LjA0NSA0LjU5LDQuNTggNC41OSwtNC41OCAxLjQxLDEuNDEgLTYsNiAtNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_bottom">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close">
</div>
</div>
</div>
`);
render($modal, ({ id }) => {

View file

@ -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 && assert.type(document.activeElement, window.HTMLElement).tagName !== "INPUT"),
rxjs.filter((e) => e.keyCode === 82 && assert.type(document.activeElement, HTMLElement).tagName !== "INPUT"),
)).pipe(rxjs.switchMap(() => lsFromHttp(path))),
),
).pipe(

View file

@ -30,7 +30,7 @@ const mutationFiles$ = new rxjs.BehaviorSubject({
class IVirtualLayer {
before() { throw new Error("NOT_IMPLEMENTED"); }
async afterSuccess() { throw new Error("NOT_IMPLEMENTED"); }
async afterError() { throw new Error("NOT_IMPLEMENTED"); }
async afterError() { return rxjs.EMPTY; }
}
export function withVirtualLayer(ajax$, mutate) {
@ -51,6 +51,9 @@ export function touch(path) {
};
return new class TouchVL extends IVirtualLayer {
/**
* @override
*/
before() {
stateAdd(virtualFiles$, basepath, {
...file,
@ -58,6 +61,9 @@ export function touch(path) {
});
}
/**
* @override
*/
async afterSuccess() {
removeLoading(virtualFiles$, basepath, filename);
onDestroy(() => statePop(virtualFiles$, basepath, filename));
@ -68,6 +74,9 @@ export function touch(path) {
hooks.mutation.emit({ op: "touch", path: basepath });
}
/**
* @override
*/
async afterError() {
statePop(virtualFiles$, basepath, filename);
return rxjs.of(fscache().remove(basepath)).pipe(
@ -87,6 +96,9 @@ export function mkdir(path) {
};
return new class MkdirVL extends IVirtualLayer {
/**
* @override
*/
before() {
stateAdd(virtualFiles$, basepath, {
...file,
@ -95,6 +107,9 @@ export function mkdir(path) {
statePop(mutationFiles$, basepath, dirname); // case: rm followed by mkdir
}
/**
* @override
*/
async afterSuccess() {
removeLoading(virtualFiles$, basepath, dirname);
onDestroy(() => statePop(virtualFiles$, basepath, dirname));
@ -105,6 +120,9 @@ export function mkdir(path) {
hooks.mutation.emit({ op: "mkdir", path: basepath });
}
/**
* @override
*/
async afterError() {
statePop(virtualFiles$, basepath, dirname);
return rxjs.of(fscache().remove(basepath)).pipe(
@ -124,6 +142,9 @@ export function save(path, size) {
};
return new class SaveVL extends IVirtualLayer {
/**
* @override
*/
before() {
stateAdd(virtualFiles$, basepath, {
...file,
@ -132,6 +153,9 @@ export function save(path, size) {
statePop(mutationFiles$, basepath, filename); // eg: rm followed by save
}
/**
* @override
*/
async afterSuccess() {
removeLoading(virtualFiles$, basepath, filename);
onDestroy(() => statePop(virtualFiles$, basepath, filename));
@ -142,6 +166,9 @@ export function save(path, size) {
hooks.mutation.emit({ op: "save", path: basepath });
}
/**
* @override
*/
async afterError() {
statePop(virtualFiles$, basepath, filename);
return rxjs.EMPTY;
@ -160,6 +187,9 @@ export function rm(...paths) {
}
return new class RmVL extends IVirtualLayer {
/**
* @override
*/
before() {
for (let i=0; i<arr.length; i+=2) {
stateAdd(mutationFiles$, arr[i], {
@ -176,6 +206,9 @@ export function rm(...paths) {
}
}
/**
* @override
*/
async afterSuccess() {
for (let i=0; i<arr.length; i+=2) {
stateAdd(mutationFiles$, arr[i], {
@ -208,6 +241,9 @@ export function rm(...paths) {
if (arr.length > 0) hooks.mutation.emit({ op: "rm", path: arr[0] });
}
/**
* @override
*/
async afterError() {
for (let i=0; i<arr.length; i+=2) {
stateAdd(mutationFiles$, arr[i], {
@ -232,6 +268,9 @@ export function mv(fromPath, toPath) {
let type = null;
return new class MvVL extends IVirtualLayer {
/**
* @override
*/
before() {
if (fromBasepath === toBasepath) this._beforeSamePath();
else this._beforeSamePath();
@ -270,6 +309,9 @@ export function mv(fromPath, toPath) {
});
}
/**
* @override
*/
async afterSuccess() {
fscache().remove(fromPath, false);
if (fromBasepath === toBasepath) await this._afterSuccessSamePath();
@ -325,6 +367,9 @@ export function mv(fromPath, toPath) {
hooks.mutation.emit({ op: "mv", path: toBasepath });
}
/**
* @override
*/
async afterError() {
statePop(mutationFiles$, fromBasepath, fromName);
if (fromBasepath !== toBasepath) {

View file

@ -4,9 +4,9 @@ import { settingsGet, settingsSave } from "../../lib/store.js";
let state$ = null;
export function init() {
state$ = new rxjs.BehaviorSubject(settingsGet({
view: window.CONFIG.default_view || "grid",
show_hidden: window.CONFIG.display_hidden || false,
sort: window.CONFIG.default_sort || "type",
view: window.CONFIG["default_view"] || "grid",
show_hidden: window.CONFIG["display_hidden"] || false,
sort: window.CONFIG["default_sort"] || "type",
order: null,
search: "",
}, "filespage"));

View file

@ -24,7 +24,6 @@ const selection$ = new rxjs.BehaviorSubject([
onDestroy(clearSelection);
export function addSelection({ shift = false, n = 0, ...rest }) {
// console.log(n, shift)
const selections = selection$.value;
const selection = { type: shift ? "range" : "anchor", n, ...rest };

View file

@ -0,0 +1,3 @@
export function init();
export function createThing(any): HTMLElement;

View file

@ -70,7 +70,7 @@ export function createThing({
}) {
const [, ext] = formatFile(name);
const mime = TYPES.MIME[ext.toLowerCase()];
const $thing = assert.type($tmpl.cloneNode(true), window.HTMLElement);
const $thing = assert.type($tmpl.cloneNode(true), 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.
@ -107,9 +107,9 @@ export function createThing({
$img.src = "api/files/cat?path=" + encodeURIComponent(path) + "&thumbnail=true" + location.search.replace("?", "&");
$img.loaded = false;
const t = new Date();
const t = new Date().getTime();
$img.onload = async() => {
const duration = new Date() - t;
const duration = new Date().getTime() - t;
$img.loaded = true;
await Promise.all([
animate($img, {

View file

@ -0,0 +1 @@
export default function(any): void;

View file

@ -2,4 +2,6 @@ interface Window {
WaveSurfer: {
create: (options: any) => any;
};
}
}
export default function(any): void;

View file

@ -2,4 +2,6 @@ interface Window {
ePub: {
Book: new (options: any) => any;
};
}
}
export default function(any): void;

View file

@ -60,7 +60,7 @@ export default function(render) {
effect(setup$.pipe(
rxjs.mergeMap(() => rxjs.merge(
rxjs.fromEvent(document, "keydown"),
rendition$.pipe(rxjs.mergeMap(() => rxjs.fromEvent(assert.type(qs(document.body, "iframe"), window.HTMLElement).contentDocument.body, "keydown"))),
rendition$.pipe(rxjs.mergeMap(() => rxjs.fromEvent(assert.type(qs(document.body, "iframe"), HTMLElement).contentDocument.body, "keydown"))),
)),
rxjs.map((e) => {
switch (e.code) {

View file

@ -6,4 +6,6 @@ interface Window {
save: (editor: any) => void;
};
};
}
}
export default function(any): void;

View file

@ -97,7 +97,7 @@ export default async function(render, { acl$ }) {
"Ctrl-X Ctrl-C": () => window.history.back(),
});
if (mode === "orgmode") {
const cleanup = CodeMirror.orgmode.init(editor);
const cleanup = window.CodeMirror.orgmode.init(editor);
onDestroy(cleanup);
}
@ -189,7 +189,7 @@ export default async function(render, { acl$ }) {
try {
await save(cm.getValue()).toPromise();
resolve();
} catch(err) {
} catch (err) {
reject(err);
return true;
}

View file

@ -2,15 +2,14 @@ import "../../../lib/vendor/codemirror/addon/mode/simple.js";
import {
org_cycle, org_shifttab, org_metaleft, org_metaright, org_meta_return, org_metaup,
org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold,
isFold, org_set_fold, org_shiftmetaleft, org_shiftmetaright,
isFold, org_shiftmetaleft, org_shiftmetaright,
} from "./emacs-org.js";
import { getCurrentPath } from "../common.js";
import { join } from "../../../lib/path.js";
import { currentShare } from "../../filespage/cache.js";
window.CodeMirror.__mode = "orgmode";
CodeMirror.defineSimpleMode("orgmode", {
window.CodeMirror.defineSimpleMode("orgmode", {
start: [
{ regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star", "header level1 org-todo", "header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"] },
{ regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star", "header org-todo", "header org-done", "header org-priority", "header", "header void", "header comment"] },
@ -34,7 +33,7 @@ CodeMirror.defineSimpleMode("orgmode", {
],
});
CodeMirror.registerHelper("fold", "orgmode", function(cm, start) {
window.CodeMirror.registerHelper("fold", "orgmode", function(cm, start) {
// init
const levelToMatch = headerLevel(start.line);
@ -54,21 +53,21 @@ CodeMirror.registerHelper("fold", "orgmode", function(cm, start) {
}
return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(end, cm.getLine(end).length),
from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: window.CodeMirror.Pos(end, cm.getLine(end).length),
};
function headerLevel(lineNo) {
const line = cm.getLine(lineNo);
const match = /^\*+/.exec(line);
if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)))) {
if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(window.CodeMirror.Pos(lineNo, 0)))) {
return match[0].length;
}
return null;
}
});
CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
window.CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
return mode.name === "orgmode";
}, function(cm, start) {
const drawer = isBeginningOfADrawer(start.line);
@ -85,8 +84,8 @@ CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
}
return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(end, cm.getLine(end).length),
from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: window.CodeMirror.Pos(end, cm.getLine(end).length),
};
function isBeginningOfADrawer(lineNo) {
@ -103,9 +102,9 @@ CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
}
});
CodeMirror.registerHelper("orgmode", "init", (editor) => {
window.CodeMirror.registerHelper("orgmode", "init", (editor) => {
editor.setOption("extraKeys", {
"Tab": (cm) => org_cycle(cm),
Tab: (cm) => org_cycle(cm),
"Shift-Tab": (cm) => org_shifttab(cm),
"Alt-Left": (cm) => org_metaleft(cm),
"Alt-Right": (cm) => org_metaright(cm),
@ -126,15 +125,15 @@ CodeMirror.registerHelper("orgmode", "init", (editor) => {
// fold everything except headers by default
editor.operation(function() {
for (let i = 0; i < editor.lineCount(); i++) {
if (/header/.test(editor.getTokenTypeAt(CodeMirror.Pos(i, 0))) === false) {
fold(editor, CodeMirror.Pos(i, 0));
if (/header/.test(editor.getTokenTypeAt(window.CodeMirror.Pos(i, 0))) === false) {
fold(editor, window.CodeMirror.Pos(i, 0));
}
}
});
return CodeMirror.orgmode.destroy.bind(this, editor);
return window.CodeMirror.orgmode.destroy.bind(this, editor);
});
CodeMirror.registerHelper("orgmode", "destroy", (editor) => {
window.CodeMirror.registerHelper("orgmode", "destroy", (editor) => {
editor.off("mousedown", toggleHandler);
editor.off("touchstart", toggleHandler);
editor.off("gutterClick", foldLine);
@ -272,7 +271,7 @@ function toggleHandler(cm, e) {
if (/^https?\:\/\//.test(src)) {
$img.src = src;
} else {
let path = join(location, src).replace(/^\/view/, "");
const path = join(location, src).replace(/^\/view/, "");
$img.src = "/api/files/cat?path=" + path;
const share = currentShare();
if (share) $img.src += "&share=" + share;

View file

@ -0,0 +1,8 @@
interface Window {
EXIF: {
getAllTags: (any) => object;
getData: (HTMLElement, any) => void;
};
}
export default function(any): void;

View file

@ -30,17 +30,20 @@ export default function(render) {
render($page);
transition(qs($page, ".component_image_container"));
const toggleInfo = () => qs($page, ".images_aside").classList.toggle("open");
const $imgContainer = qs($page, ".images_wrapper");
const $photo = qs($page, "img.photo");
const removeLoader = createLoader($imgContainer);
const load$ = new rxjs.BehaviorSubject(null);
const toggleInfo = () => {
qs($page, ".images_aside").classList.toggle("open");
componentMetadata(createRender(qs($page, ".images_aside")), { toggle: toggleInfo, load$ });
};
renderMenubar(
qs($page, "component-menubar"),
buttonDownload(getFilename(), getDownloadUrl()),
buttonFullscreen(qs($page, ".component_image_container")),
buttonInfo({ $img: $photo, toggle: toggleInfo }),
buttonInfo({ toggle: toggleInfo }),
);
effect(onLoad($photo).pipe(
@ -59,7 +62,7 @@ export default function(render) {
],
})),
rxjs.catchError((err) => {
if (err.target instanceof window.HTMLElement && err.type === "error") {
if (err.target instanceof HTMLElement && err.type === "error") {
return rxjs.of($photo).pipe(
removeLoader,
rxjs.tap(($img) => {
@ -79,11 +82,10 @@ export default function(render) {
}),
));
componentMetadata(createRender(qs($page, ".images_aside")), { toggle: toggleInfo, load$ });
componentPager(createRender(qs($page, ".component_pager")));
}
function buttonInfo({ $img, toggle }) {
function buttonInfo({ toggle }) {
const $el = createElement(`
<span>
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg4MiwwLDAsMC44ODIsNS45LDUuOSkiPgogICAgPHBhdGggc3R5bGU9ImZpbGw6I2YyZjJmMjtmaWxsLW9wYWNpdHk6MSIgZD0ibSA2Mi4xNjIsMCBjIDYuNjk2LDAgMTAuMDQzLDQuNTY3IDEwLjA0Myw5Ljc4OSAwLDYuNTIyIC01LjgxNCwxMi41NTUgLTEzLjM5MSwxMi41NTUgLTYuMzQ0LDAgLTEwLjA0NSwtMy43NTIgLTkuODY5LC05Ljk0NyBDIDQ4Ljk0NSw3LjE3NiA1My4zNSwwIDYyLjE2MiwwIFogTSA0MS41NDMsMTAwIGMgLTUuMjg3LDAgLTkuMTY0LC0zLjI2MiAtNS40NjMsLTE3LjYxNSBsIDYuMDcsLTI1LjQ1NyBjIDEuMDU3LC00LjA3NyAxLjIzLC01LjcwNyAwLC01LjcwNyAtMS41ODgsMCAtOC40NTEsMi44MTYgLTEyLjUxLDUuNTkgTCAyNyw1Mi40MDYgQyAzOS44NjMsNDEuNDggNTQuNjYyLDM1LjA3MiA2MS4wMDQsMzUuMDcyIGMgNS4yODUsMCA2LjE2OCw2LjM2MSAzLjUyNSwxNi4xNDggTCA1Ny41OCw3Ny45OCBjIC0xLjIzNCw0LjcyOSAtMC43MDMsNi4zNTkgMC41MjcsNi4zNTkgMS41ODYsMCA2Ljc4NywtMS45NjMgMTEuODk2LC02LjA0MSBMIDczLDgyLjM3NyBDIDYwLjQ4OCw5NS4xIDQ2LjgzLDEwMCA0MS41NDMsMTAwIFoiIC8+CiAgPC9nPgo8L3N2Zz4K" alt="info">

View file

@ -1,7 +1,6 @@
import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js";
import rxjs, { effect, onClick, onLoad } from "../../lib/rx.js";
import { createElement, createRender } from "../../lib/skeleton/index.js";
import rxjs, { effect, onClick } from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import assert from "../../lib/assert.js";
import t from "../../locales/index.js";
import { loadJS, loadCSS } from "../../helpers/loader.js";
@ -30,7 +29,7 @@ function componentHeader(render, { toggle }) {
`);
render($header);
effect(onClick($header, `[alt="close"]`).pipe(rxjs.tap(toggle)));
effect(onClick(qs($header, `[alt="close"]`)).pipe(rxjs.tap(toggle)));
}
function componentBody(render, { load$ }) {
@ -54,7 +53,7 @@ function componentBody(render, { load$ }) {
`);
render($page);
effect(load$.pipe(rxjs.tap(async ($img) => {
effect(load$.pipe(rxjs.tap(async($img) => {
if (!$img) return;
const metadata = await extractExif($img);
qs($page, `[data-bind="date"]`).innerText = formatDate(metadata.date) || "-";
@ -72,7 +71,7 @@ async function componentMap(render, { metadata }) {
if (!d || d.length !== 4) return null;
const [degrees, minutes, seconds, direction] = d;
const dd = degrees + minutes/60 + seconds/(60*60);
return direction == "S" || direction == "W" ? -dd : dd;
return direction === "S" || direction === "W" ? -dd : dd;
};
const lat = DMSToDD(metadata.location[0]);
const lng = DMSToDD(metadata.location[1]);
@ -100,9 +99,11 @@ async function componentMap(render, { metadata }) {
await new Promise((resolve) => setTimeout(resolve, 500));
const TILE_SERVER = "https://tile.openstreetmap.org/${z}/${x}/${y}.png";
const TILE_SIZE = parseInt($page.clientWidth / 3 * 100) / 100;
const TILE_SIZE = Math.floor($page.clientWidth / 3 * 100) / 100;
if (TILE_SIZE === 0) return;
$page.style.height = "${TILE_SIZE*3}px;";
const mapper = function map_url(lat, lng, zoom) {
const defaultTo = (val, def) => val === undefined ? val : def;
const mapper = (function map_url(lat, lng, zoom) {
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenamse
const n = Math.pow(2, zoom);
const tile_numbers = [
@ -113,19 +114,21 @@ async function componentMap(render, { metadata }) {
return {
tile: function(tile_server, x = 0, y = 0) {
return tile_server
.replace("${x}", Math.floor(tile_numbers[0])+x)
.replace("${y}", Math.floor(tile_numbers[1])+y)
.replace("${x}", Math.floor(tile_numbers[0] || 0)+x)
.replace("${y}", Math.floor(tile_numbers[1] || 0)+y)
.replace("${z}", Math.floor(zoom));
},
position: function() {
const t0 = defaultTo(tile_numbers[0], 0);
const t1 = defaultTo(tile_numbers[1], 0);
return [
tile_numbers[0] - Math.floor(tile_numbers[0]),
tile_numbers[1] - Math.floor(tile_numbers[1]),
t0 - Math.floor(t0),
t1 - Math.floor(t1),
];
},
};
}(lat, lng, 11);
const center = (position, i) => parseInt(TILE_SIZE * (1 + position[i]) * 1000)/1000;
}(lat, lng, 11));
const center = (position, i) => Math.floor(TILE_SIZE * (1 + position[i]) * 1000)/1000;
const $tiles = createElement(`
<div class="bigpicture">
<div class="line">
@ -146,9 +149,10 @@ async function componentMap(render, { metadata }) {
</div>
`);
qs($page, `[data-bind="maptile"]`).appendChild($tiles);
const pos = mapper.position();
qs($page, ".marker").setAttribute("style", `
left: ${TILE_SIZE * (1 + mapper.position()[0]) - 15}px;
top: ${TILE_SIZE * (1 + mapper.position()[1]) - 30}px;
left: ${TILE_SIZE * (1 + defaultTo(pos[0], 0)) - 15}px;
top: ${TILE_SIZE * (1 + defaultTo(pos[1], 0)) - 30}px;
`);
$tiles.setAttribute("style", `transform-origin: ${center(mapper.position(), 0)}px ${center(mapper.position(), 1)}px;`);
}
@ -166,13 +170,13 @@ function componentMore(render, { metadata }) {
const formatValue = (str) => {
if (!metadata.all || metadata.all[str] === undefined) return "-";
if (typeof metadata.all[str] === "number") {
return parseInt(metadata.all[str]*100)/100;
return Math.floor(metadata.all[str]*100)/100;
} else if (metadata.all[str].denominator !== undefined &&
metadata.all[str].numerator !== undefined) {
if (metadata.all[str].denominator === 1) {
return metadata.all[str].numerator;
} else if (metadata.all[str].numerator > metadata.all[str].denominator) {
return parseInt(
return Math.floor(
metadata.all[str].numerator * 10 / metadata.all[str].denominator,
) / 10;
} else {
@ -195,8 +199,8 @@ function componentMore(render, { metadata }) {
if (a.toLowerCase().trim() < b.toLowerCase().trim()) return -1;
else if (a.toLowerCase().trim() > b.toLowerCase().trim()) return +1;
return 0;
}).map((key) => {
switch(key) {
}).forEach((key) => {
switch (key) {
case "undefined":
case "thumbnail":
break;
@ -218,18 +222,25 @@ export function init() {
]);
}
const extractExif = ($img) => new Promise((resolve) => EXIF.getData($img, function(data) {
const metadata = EXIF.getAllTags(this);
const to_date = (str) => {
if (!str) return null;
return new Date(...str.split(/[ :]/));
const extractExif = ($img) => new Promise((resolve) => window.EXIF.getData($img, function() {
const metadata = window.EXIF.getAllTags($img);
const to_date = (str = "") => {
const digits = str.split(/[ :]/).map((digit) => parseInt(digit));
return new Date(
digits[0] || 0,
digits[1] || 0,
digits[2] || 0,
digits[3] || 0,
digits[4] || 0,
digits[5] || 0,
);
};
resolve({
date: to_date(
metadata["DateTime"] || metadata["DateTimeDigitized"] ||
metadata["DateTimeOriginal"] || metadata["GPSDateStamp"],
),
location: metadata["GPSLatitude"] && metadata["GPSLongitude"] && [
location: (metadata["GPSLatitude"] && metadata["GPSLongitude"] && [
[
metadata["GPSLatitude"][0], metadata["GPSLatitude"][1],
metadata["GPSLatitude"][2], metadata["GPSLatitudeRef"],
@ -238,34 +249,34 @@ const extractExif = ($img) => new Promise((resolve) => EXIF.getData($img, functi
metadata["GPSLongitude"][0], metadata["GPSLongitude"][1],
metadata["GPSLongitude"][2], metadata["GPSLongitudeRef"],
],
] || null,
]) || null,
maker: metadata["Make"] || null,
model: metadata["Model"] || null,
focal: metadata["FocalLength"] || null,
aperture: metadata["FNumber"] || null,
shutter: metadata["ExposureTime"] || null,
iso: metadata["ISOSpeedRatings"] || null,
dimension: metadata["PixelXDimension"] && metadata["PixelYDimension"] && [
dimension: (metadata["PixelXDimension"] && metadata["PixelYDimension"] && [
metadata["PixelXDimension"],
metadata["PixelYDimension"],
] || null,
]) || null,
all: Object.keys(metadata).length === 0 ? null : metadata,
});
}));
const formatTime = (t) => t && t.toLocaleTimeString(
const formatTime = (t) => t?.toLocaleTimeString(
"en-us",
{ weekday: "short", hour: "2-digit", minute: "2-digit" },
);
const formatDate = (t) => t && t.toLocaleDateString(
const formatDate = (t) => t?.toLocaleDateString(
navigator.language,
{ day: "numeric", year: "numeric", month: "short", day: "numeric" },
{ year: "numeric", month: "short", day: "numeric" },
);
const formatCameraSettings = (metadata) => {
let str = format("model", metadata);
const f = format("focal", metadata)
const str = format("model", metadata);
const f = format("focal", metadata);
if (!f) return str;
return `${str} (${f})`;
};
@ -280,17 +291,17 @@ const formatCameraName = (metadata) => {
const format = (key, metadata) => {
if (!metadata[key]) return "";
switch(key) {
switch (key) {
case "focal":
return `${metadata.focal}mm`;
case "iso":
return `ISO${metadata.iso}`;
case "aperture":
return "ƒ"+parseInt(metadata.aperture*10)/10;
return `ƒ${Math.floor(metadata.aperture*10)/10}`;
case "shutter":
if (metadata.shutter > 60) return metadata.shutter+"m";
else if (metadata.shutter > 1) return metadata.shutter+"s";
return "1/"+parseInt(metadata.shutter.denominator / metadata.shutter.numerator)+"s";
return `1/${Math.floor(metadata.shutter.denominator / metadata.shutter.numerator)}s`;
case "dimension":
if (metadata.dimension.length !== 2 || !metadata.dimension[0] || !metadata.dimension[1]) return "-";
return metadata.dimension[0]+"x"+metadata.dimension[1];

View file

@ -8,4 +8,6 @@ interface Window {
};
env?: string;
chrome: object;
}
}
export default function(any): void;

View file

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

View file

@ -1,129 +0,0 @@
const CACHE_NAME = "v0.3";
/*
* Control everything going through the wire, applying different
* strategy for caching, fetching resources
*/
self.addEventListener("fetch", function(event) {
const errResponse = (err) => (new Response(JSON.stringify({
code: "CANNOT_LOAD",
message: err.message
}), { status: 502 }));
if (is_a_ressource(event.request)) {
return event.respondWith(cacheFirstStrategy(event).catch(errResponse));
} else if (is_an_api_call(event.request)) {
return event;
} else if (is_an_index(event.request)) {
return event.respondWith(cacheFirstStrategy(event).catch(errResponse));
} else {
return event;
}
});
/*
* When a new service worker is coming in, we need to do a bit of
* cleanup to get rid of the rotten cache
*/
self.addEventListener("activate", function(event) {
vacuum(event);
});
self.addEventListener("error", function(err) {
console.error(err);
});
/*
* When a newly installed service worker is coming in, we want to use it
* straight away (make it active). By default it would be in a "waiting state"
*/
self.addEventListener("install", function() {
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll([
"/",
"/api/config"
]);
});
if (self.skipWaiting) {
self.skipWaiting();
}
});
function is_a_ressource(request) {
const p = _pathname(request);
if (["assets", "manifest.json", "favicon.ico"].indexOf(p[0]) !== -1) {
return true;
} else if (p[0] === "api" && (p[1] === "config")) {
return true;
}
return false;
}
function is_an_api_call(request) {
return _pathname(request)[0] === "api";
}
function is_an_index(request) {
return ["files", "view", "login", "logout", ""]
.indexOf(_pathname(request)[0]) >= 0;
}
// //////////////////////////////////////
// HELPERS
// //////////////////////////////////////
function vacuum(event) {
return event.waitUntil(
caches.keys().then(function(cachesName) {
return Promise.all(cachesName.map(function(cacheName) {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
return null;
}));
})
);
}
function _pathname(request) {
return request.url.replace(/^http[s]?:\/\/[^\/]*\//, "").split("/");
}
/*
* strategy is cache first:
* 1. use whatever is in the cache
* 2. perform the network call to update the cache
*/
function cacheFirstStrategy(event) {
return caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function(response) {
if (!response) {
return fetchAndCache(event);
}
fetchAndCache(event).catch(nil);
return response;
});
});
function fetchAndCache(event) {
// A request is a stream and can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need to clone the response as
// seen on:
// https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
return fetch(event.request)
.then(function(response) {
if (!response || response.status !== 200) {
return response;
}
// A response is a stream and can only because we want the browser to consume the
// response as well as the cache consuming the response, we need to clone it
const responseClone = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
return response;
});
}
function nil() {}
}

1
public/global.d.ts vendored
View file

@ -1,4 +1,5 @@
interface Window {
chrome: object;
overrides: {
[key: string]: any;
"xdg-open"?: (mime: string) => void;

View file

@ -9,10 +9,10 @@
<link rel="stylesheet" href="custom.css">
{{ if eq .license "agpl" }}
<script>
class NyanCatLoader extends window.HTMLElement {
class NyanCatLoader extends HTMLElement {
connectedCallback() {
this.innerHTML = this.render();
this.timeout = window.setTimeout(function(){
this.timeout = setTimeout(function(){
const $rbw = document.querySelector("#rbw .w");
$rbw.innerHTML = $rbw.innerHTML.repeat(10);
@ -22,7 +22,7 @@
}
disconnectedCallback() {
window.clearTimeout(this.timeout);
clearTimeout(this.timeout);
}
render() {

View file

@ -1,11 +1,21 @@
{
"compilerOptions": {
"allowJs": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"checkJs": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noEmit": true,
"noErrorTruncation": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUncheckedIndexedAccess": true,
@ -13,9 +23,7 @@
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strict": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"strict": true,
"lib": [
"dom",
"dom.iterable",

View file

@ -6,13 +6,7 @@ export default defineConfig(({ comand, mode }) => {
test: {
global: true,
environment: "jsdom",
setupFiles: ["./test/setup.js"],
},
coverage: {
reporter: ["html"],
exclude: [
"./assets/lib/vendor/**"
],
setupFiles: ["./vite.setup.js"],
}
};
});

17
public/vite.setup.js Normal file
View file

@ -0,0 +1,17 @@
import {
describe, it, test, expect, vi,
afterEach, afterAll, beforeEach, beforeAll,
} from "vitest";
global.nextTick = () => new Promise((done) => setTimeout(done, 0));
global.requestAnimationFrame = (callback) => setTimeout(callback, 0);
global.describe = describe;
global.it = it;
global.test = test;
global.expect = expect;
global.vi = vi;
global.beforeEach = beforeEach;
global.beforeAll = beforeAll;
global.afterEach = afterEach;
global.afterAll = afterAll;