less of any
This commit is contained in:
parent
116d5b9b00
commit
544b7d522c
54
lib/api.ts
54
lib/api.ts
@ -5,7 +5,8 @@ import { TYPE_LINK, TYPE_MEDIA } from "./constants";
|
||||
import { filters } from "./filters";
|
||||
import { Prefs } from "./prefs";
|
||||
import { lazy } from "./util";
|
||||
import { Item, makeUniqueItems } from "./item";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Item, makeUniqueItems, BaseItem } from "./item";
|
||||
import { getManager } from "./manager/man";
|
||||
import { select } from "./select";
|
||||
import { single } from "./single";
|
||||
@ -16,12 +17,17 @@ import { _ } from "./i18n";
|
||||
|
||||
const MAX_BATCH = 10000;
|
||||
|
||||
export interface QueueOptions {
|
||||
mask?: string;
|
||||
paused?: boolean;
|
||||
}
|
||||
|
||||
export const API = new class APIImpl {
|
||||
async filter(arr: any, type: number) {
|
||||
async filter(arr: BaseItem[], type: number) {
|
||||
return await (await filters()).filterItemsByType(arr, type);
|
||||
}
|
||||
|
||||
async queue(items: any, options: any) {
|
||||
async queue(items: BaseItem[], options: QueueOptions) {
|
||||
await MASK.init();
|
||||
const {mask = MASK.current} = options;
|
||||
|
||||
@ -36,12 +42,9 @@ export const API = new class APIImpl {
|
||||
fileName: null,
|
||||
title: "",
|
||||
description: "",
|
||||
fromMetalink: false,
|
||||
startDate: new Date(),
|
||||
hashes: [],
|
||||
private: false,
|
||||
postData: null,
|
||||
cleanRequest: false,
|
||||
mask,
|
||||
date: Date.now(),
|
||||
paused
|
||||
@ -54,7 +57,7 @@ export const API = new class APIImpl {
|
||||
}
|
||||
return currentBatch;
|
||||
});
|
||||
items = items.map((i: any) => {
|
||||
items = items.map(i => {
|
||||
delete i.idx;
|
||||
return new Item(i, defaults);
|
||||
});
|
||||
@ -79,7 +82,7 @@ export const API = new class APIImpl {
|
||||
}
|
||||
}
|
||||
|
||||
sanity(links: any[], media: any[]) {
|
||||
sanity(links: BaseItem[], media: BaseItem[]) {
|
||||
if (!links.length && !media.length) {
|
||||
new Notification(null, _("no-links"));
|
||||
return false;
|
||||
@ -87,7 +90,7 @@ export const API = new class APIImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
async turbo(links: any[], media: any[]) {
|
||||
async turbo(links: BaseItem[], media: BaseItem[]) {
|
||||
if (!this.sanity(links, media)) {
|
||||
return false;
|
||||
}
|
||||
@ -105,37 +108,36 @@ export const API = new class APIImpl {
|
||||
return await this.queue(selected, {paused: await Prefs.get("add-paused")});
|
||||
}
|
||||
|
||||
async regularInternal(selected: any) {
|
||||
if (selected.mask && !selected.maskOnce) {
|
||||
async regularInternal(selected: BaseItem[], options: any) {
|
||||
if (options.mask && !options.maskOnce) {
|
||||
await MASK.init();
|
||||
await MASK.push(selected.mask);
|
||||
await MASK.push(options.mask);
|
||||
}
|
||||
if (typeof selected.fast === "string" && !selected.fastOnce) {
|
||||
if (typeof options.fast === "string" && !options.fastOnce) {
|
||||
await FASTFILTER.init();
|
||||
await FASTFILTER.push(selected.fast);
|
||||
await FASTFILTER.push(options.fast);
|
||||
}
|
||||
if (typeof selected.type === "string") {
|
||||
await Prefs.set("last-type", selected.type);
|
||||
if (typeof options.type === "string") {
|
||||
await Prefs.set("last-type", options.type);
|
||||
}
|
||||
const {items} = selected;
|
||||
delete selected.items;
|
||||
return await this.queue(items, selected);
|
||||
return await this.queue(selected, options);
|
||||
}
|
||||
|
||||
async regular(links: any[], media: any[]) {
|
||||
async regular(links: BaseItem[], media: BaseItem[]) {
|
||||
if (!this.sanity(links, media)) {
|
||||
return false;
|
||||
}
|
||||
const selected = await select(links, media);
|
||||
return this.regularInternal(selected);
|
||||
const {items, options} = await select(links, media);
|
||||
console.log(items, options);
|
||||
return this.regularInternal(items, options);
|
||||
}
|
||||
|
||||
async singleTurbo(item: any) {
|
||||
async singleTurbo(item: BaseItem) {
|
||||
return await this.queue([item], {paused: await Prefs.get("add-paused")});
|
||||
}
|
||||
|
||||
async singleRegular(item: any) {
|
||||
const selected = await single(item);
|
||||
return this.regularInternal(selected);
|
||||
async singleRegular(item: BaseItem | null) {
|
||||
const {items, options} = await single(item);
|
||||
return this.regularInternal(items, options);
|
||||
}
|
||||
}();
|
||||
|
@ -13,9 +13,14 @@ import {
|
||||
browserAction as action,
|
||||
menus as _menus, contextMenus as _cmenus,
|
||||
tabs,
|
||||
webNavigation as nav
|
||||
webNavigation as nav,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Tab,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MenuClickInfo
|
||||
} from "./browser";
|
||||
import { Bus } from "./bus";
|
||||
import { filterInSitu } from "./util";
|
||||
|
||||
|
||||
const menus = typeof (_menus) !== "undefined" && _menus || _cmenus;
|
||||
@ -23,7 +28,7 @@ const menus = typeof (_menus) !== "undefined" && _menus || _cmenus;
|
||||
const GATHER = "/bundles/content-gather.js";
|
||||
|
||||
|
||||
async function runContentJob(tab: any, file: string, msg: any) {
|
||||
async function runContentJob(tab: Tab, file: string, msg: any) {
|
||||
try {
|
||||
const res = await tabs.executeScript(tab.id, {
|
||||
file,
|
||||
@ -56,7 +61,7 @@ type SelectionOptions = {
|
||||
selectionOnly: boolean;
|
||||
allTabs: boolean;
|
||||
turbo: boolean;
|
||||
tab: any;
|
||||
tab: Tab;
|
||||
};
|
||||
|
||||
|
||||
@ -71,9 +76,8 @@ class Handler {
|
||||
return makeUniqueItems(
|
||||
results.filter(e => e[what]).map(e => {
|
||||
const finisher = new Finisher(e);
|
||||
return e[what].
|
||||
map((item: any) => finisher.finish(item)).
|
||||
filter((i: any) => i);
|
||||
return filterInSitu(e[what].
|
||||
map((item: any) => finisher.finish(item)), e => !!e);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -348,7 +352,7 @@ locale.then(() => {
|
||||
}
|
||||
}
|
||||
|
||||
async findSingleItem(tab: any, url: string, turbo = false) {
|
||||
async findSingleItem(tab: Tab, url: string, turbo = false) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
@ -368,7 +372,7 @@ locale.then(() => {
|
||||
API[turbo ? "singleTurbo" : "singleRegular"](item);
|
||||
}
|
||||
|
||||
onClicked(info: any, tab: any) {
|
||||
onClicked(info: MenuClickInfo, tab: Tab) {
|
||||
if (!tab.id) {
|
||||
return;
|
||||
}
|
||||
@ -378,7 +382,7 @@ locale.then(() => {
|
||||
console.error("Invalid Handler for", menuItemId);
|
||||
return;
|
||||
}
|
||||
const rv = handler.call(this, info, tab);
|
||||
const rv: Promise<void> | void = handler.call(this, info, tab);
|
||||
if (rv && rv.catch) {
|
||||
rv.catch(console.error);
|
||||
}
|
||||
@ -394,7 +398,7 @@ locale.then(() => {
|
||||
}, tab[0]);
|
||||
}
|
||||
|
||||
async onClickedDTARegular(info: any, tab: any) {
|
||||
async onClickedDTARegular(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: false,
|
||||
@ -403,7 +407,7 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularAll(info: any, tab: any) {
|
||||
async onClickedDTARegularAll(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
@ -412,7 +416,7 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularSelection(info: any, tab: any) {
|
||||
async onClickedDTARegularSelection(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
@ -421,7 +425,7 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurbo(info: any, tab: any) {
|
||||
async onClickedDTATurbo(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: false,
|
||||
@ -430,7 +434,7 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurboAll(info: any, tab: any) {
|
||||
async onClickedDTATurboAll(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
@ -439,7 +443,7 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurboSelection(info: any, tab: any) {
|
||||
async onClickedDTATurboSelection(info: MenuClickInfo, tab: Tab) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
@ -448,28 +452,46 @@ locale.then(() => {
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, false);
|
||||
async onClickedDTARegularLink(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.linkUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.linkUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, true);
|
||||
async onClickedDTATurboLink(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.linkUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.linkUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
async onClickedDTARegularImage(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.srcUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
async onClickedDTATurboImage(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.srcUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
async onClickedDTARegularMedia(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.srcUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
async onClickedDTATurboMedia(info: MenuClickInfo, tab: Tab) {
|
||||
if (!info.srcUrl) {
|
||||
return;
|
||||
}
|
||||
await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
onClickedDTAAdd() {
|
||||
|
@ -98,9 +98,9 @@ export class BatchGenerator implements Generator {
|
||||
|
||||
public readonly hasInvalid: boolean;
|
||||
|
||||
public readonly length: any;
|
||||
public readonly length: number;
|
||||
|
||||
public readonly preview: any;
|
||||
public readonly preview: string;
|
||||
|
||||
constructor(str: string) {
|
||||
this.gens = [];
|
||||
|
@ -3,6 +3,42 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const polyfill = require("webextension-polyfill");
|
||||
|
||||
interface ExtensionListener {
|
||||
addListener: (listener: Function) => void;
|
||||
removeListener: (listener: Function) => void;
|
||||
}
|
||||
|
||||
export interface MessageSender {
|
||||
tab?: Tab;
|
||||
frameId?: number;
|
||||
id?: number;
|
||||
url?: string;
|
||||
tlsChannelId?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface Tab {
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface MenuClickInfo {
|
||||
menuItemId: string | number;
|
||||
button?: number;
|
||||
linkUrl?: string;
|
||||
srcUrl?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface RawPort {
|
||||
error: any;
|
||||
name: string;
|
||||
onDisconnect: ExtensionListener;
|
||||
onMessage: ExtensionListener;
|
||||
sender?: MessageSender;
|
||||
disconnect: () => void;
|
||||
postMessage: (message: any) => void;
|
||||
}
|
||||
|
||||
export const {extension} = polyfill;
|
||||
export const {notifications} = polyfill;
|
||||
export const {browserAction} = polyfill;
|
||||
|
35
lib/bus.ts
35
lib/bus.ts
@ -2,22 +2,18 @@
|
||||
// License: MIT
|
||||
|
||||
import { EventEmitter } from "./events";
|
||||
import {runtime, tabs} from "./browser";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import {runtime, tabs, RawPort, MessageSender} from "./browser";
|
||||
|
||||
export class Port extends EventEmitter {
|
||||
private port: any;
|
||||
private port: RawPort | null;
|
||||
|
||||
constructor(port: any) {
|
||||
constructor(port: RawPort) {
|
||||
super();
|
||||
this.port = port;
|
||||
|
||||
let disconnected = false;
|
||||
let tabListener: any;
|
||||
const disconnect = () => {
|
||||
if (tabListener) {
|
||||
tabs.onRemoved.removeListener(tabListener);
|
||||
tabListener = null;
|
||||
}
|
||||
if (disconnected) {
|
||||
return;
|
||||
}
|
||||
@ -41,12 +37,17 @@ export class Port extends EventEmitter {
|
||||
}
|
||||
|
||||
get name() {
|
||||
if (!this.port) {
|
||||
return null;
|
||||
}
|
||||
return this.port.name;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.port.sender && (
|
||||
this.port.sender.id || this.port.sender.extensionId);
|
||||
if (!this.port || !this.port.sender) {
|
||||
return null;
|
||||
}
|
||||
return this.port.sender.id;
|
||||
}
|
||||
|
||||
get isSelf() {
|
||||
@ -54,6 +55,9 @@ export class Port extends EventEmitter {
|
||||
}
|
||||
|
||||
post(msg: string, ...data: any[]) {
|
||||
if (!this.port) {
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
this.port.postMessage({msg});
|
||||
return;
|
||||
@ -65,14 +69,17 @@ export class Port extends EventEmitter {
|
||||
}
|
||||
|
||||
onMessage(message: any) {
|
||||
if (Object.keys(message).includes("msg")) {
|
||||
this.emit(message.msg, message);
|
||||
if (!this.port) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(message)) {
|
||||
message.forEach(this.onMessage, this);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(message).includes("msg")) {
|
||||
this.emit(message.msg, message);
|
||||
return;
|
||||
}
|
||||
if (typeof message === "string") {
|
||||
this.emit(message);
|
||||
return;
|
||||
@ -100,7 +107,7 @@ export const Bus = new class extends EventEmitter {
|
||||
runtime.onConnect.addListener(this.onConnect.bind(this));
|
||||
}
|
||||
|
||||
onMessage(msg: any, sender: any, callback: any) {
|
||||
onMessage(msg: any, sender: MessageSender, callback: any) {
|
||||
let {type = null} = msg;
|
||||
if (!type) {
|
||||
type = msg;
|
||||
@ -108,7 +115,7 @@ export const Bus = new class extends EventEmitter {
|
||||
this.emit(type, msg, callback);
|
||||
}
|
||||
|
||||
onConnect(port: any) {
|
||||
onConnect(port: RawPort) {
|
||||
if (!port.name) {
|
||||
port.disconnect();
|
||||
return;
|
||||
|
@ -1,5 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseItem } from "./item";
|
||||
|
||||
// License: MIT
|
||||
|
||||
const VERSION = 1;
|
||||
@ -40,12 +43,12 @@ export const DB = new class DB {
|
||||
});
|
||||
}
|
||||
|
||||
getAllInternal(resolve: (items: any[]) => void, reject: Function) {
|
||||
getAllInternal(resolve: (items: BaseItem[]) => void, reject: Function) {
|
||||
if (!this.db) {
|
||||
reject(new Error("db closed"));
|
||||
return;
|
||||
}
|
||||
const items: any[] = [];
|
||||
const items: BaseItem[] = [];
|
||||
const transaction = this.db.transaction(STORE, "readonly");
|
||||
transaction.onerror = ex => reject(ex);
|
||||
const store = transaction.objectStore(STORE);
|
||||
|
@ -12,6 +12,8 @@ import { Overlayable } from "./objectoverlay";
|
||||
import * as DEFAULT_FILTERS from "../data/filters.json";
|
||||
import { FASTFILTER } from "./recentlist";
|
||||
import { _, locale } from "./i18n";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseItem } from "./item";
|
||||
|
||||
const REG_ESCAPE = /[{}()[\]\\^$.]/g;
|
||||
const REG_FNMATCH = /[*?]/;
|
||||
@ -174,25 +176,37 @@ export class Matcher {
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
matchItem(item: any) {
|
||||
matchItem(item: BaseItem) {
|
||||
const {usable = "", title = "", description = "", fileName = ""} = item;
|
||||
return this.match(usable) || this.match(title) ||
|
||||
this.match(description) || this.match(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
interface RawFilter extends Object {
|
||||
active: boolean;
|
||||
type: number;
|
||||
label: string;
|
||||
expr: string;
|
||||
icon?: string;
|
||||
custom?: boolean;
|
||||
isOverridden?: (prop: string) => boolean;
|
||||
reset?: () => void;
|
||||
toJSON?: () => any;
|
||||
}
|
||||
|
||||
export class Filter {
|
||||
private readonly owner: Filters;
|
||||
|
||||
public readonly id: any;
|
||||
public readonly id: string | symbol;
|
||||
|
||||
private readonly raw: any;
|
||||
private readonly raw: RawFilter;
|
||||
|
||||
private _label: string;
|
||||
|
||||
private _reg: Matcher;
|
||||
|
||||
constructor(owner: Filters, id: any, raw: any) {
|
||||
constructor(owner: Filters, id: string | symbol, raw: RawFilter) {
|
||||
if (!owner || !id || !raw) {
|
||||
throw new Error("null argument");
|
||||
}
|
||||
@ -204,10 +218,12 @@ export class Filter {
|
||||
|
||||
init() {
|
||||
this._label = this.raw.label;
|
||||
if (this.id !== FAST && this.id.startsWith("deffilter-") &&
|
||||
!this.raw.isOverridden("label")) {
|
||||
if (typeof this.raw.isOverridden !== "undefined" &&
|
||||
typeof this.id === "string") {
|
||||
if (this.id.startsWith("deffilter-") && !this.raw.isOverridden("label")) {
|
||||
this._label = _(this.id) || this._label;
|
||||
}
|
||||
}
|
||||
this._reg = Matcher.fromExpression(this.expr);
|
||||
Object.seal(this);
|
||||
}
|
||||
@ -282,7 +298,7 @@ export class Filter {
|
||||
}
|
||||
|
||||
async reset() {
|
||||
if (this.raw.custom) {
|
||||
if (!this.raw.reset) {
|
||||
throw Error("Cannot reset non-default filter");
|
||||
}
|
||||
this.raw.reset();
|
||||
@ -292,7 +308,10 @@ export class Filter {
|
||||
|
||||
async "delete"() {
|
||||
if (!this.raw.custom) {
|
||||
throw Error("Cannot delete default filter");
|
||||
throw new Error("Cannot delete default filter");
|
||||
}
|
||||
if (typeof this.id !== "string") {
|
||||
throw new Error("Cannot delete symbolized");
|
||||
}
|
||||
await this.owner.delete(this.id);
|
||||
}
|
||||
@ -301,7 +320,7 @@ export class Filter {
|
||||
return this._reg.match(str);
|
||||
}
|
||||
|
||||
matchItem(item: any) {
|
||||
matchItem(item: BaseItem) {
|
||||
return this._reg.matchItem(item);
|
||||
}
|
||||
|
||||
@ -316,8 +335,7 @@ class FastFilter extends Filter {
|
||||
throw new Error("Invalid fast filter value");
|
||||
}
|
||||
super(owner, FAST, {
|
||||
id: FAST,
|
||||
label: FAST,
|
||||
label: "fast",
|
||||
type: TYPE_ALL,
|
||||
active: true,
|
||||
expr: value,
|
||||
@ -409,11 +427,11 @@ class Filters extends EventEmitter {
|
||||
await this.save();
|
||||
}
|
||||
|
||||
"get"(id: any) {
|
||||
"get"(id: string | symbol) {
|
||||
return this.filters.find(e => e.id === id);
|
||||
}
|
||||
|
||||
async "delete"(id: any) {
|
||||
async "delete"(id: string) {
|
||||
const idx = this.filters.findIndex(e => e.id === id);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
@ -517,7 +535,7 @@ class Filters extends EventEmitter {
|
||||
this.regenerate();
|
||||
}
|
||||
|
||||
async filterItemsByType(items: any[], type: number) {
|
||||
async filterItemsByType(items: BaseItem[], type: number) {
|
||||
const matcher = this.typeMatchers.get(type);
|
||||
const fast = await this.getFastFilter();
|
||||
return items.filter(function(item) {
|
||||
@ -544,12 +562,14 @@ class Filters extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
let _filters: any;
|
||||
let _filters: Filters;
|
||||
let _loader: Promise<void>;
|
||||
|
||||
export async function filters(): Promise<Filters> {
|
||||
if (!_filters) {
|
||||
if (!_loader) {
|
||||
_filters = new Filters();
|
||||
await _filters.load();
|
||||
_loader = _filters.load();
|
||||
}
|
||||
await _loader;
|
||||
return _filters;
|
||||
}
|
||||
|
24
lib/item.ts
24
lib/item.ts
@ -4,18 +4,32 @@
|
||||
import { ALLOWED_SCHEMES } from "./constants";
|
||||
import { TRANSFERABLE_PROPERTIES } from "./constants";
|
||||
|
||||
export interface BaseItem {
|
||||
url: string;
|
||||
usable: string;
|
||||
referrer?: string;
|
||||
usableReferrer?: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
fileName?: string;
|
||||
batch?: number;
|
||||
idx: number;
|
||||
mask?: string;
|
||||
startDate?: number;
|
||||
private?: boolean;
|
||||
postData?: string;
|
||||
paused?: boolean;
|
||||
}
|
||||
|
||||
const OPTIONPROPS = Object.freeze([
|
||||
"referrer", "usableReferrer",
|
||||
"description", "title",
|
||||
"fileName",
|
||||
"batch", "idx",
|
||||
"mask",
|
||||
"fromMetalink",
|
||||
"startDate",
|
||||
"hashes",
|
||||
"private",
|
||||
"postData",
|
||||
"cleanRequest",
|
||||
"paused"
|
||||
]);
|
||||
|
||||
@ -34,7 +48,7 @@ function maybeAssign(options: any, what: any) {
|
||||
this[what] = val;
|
||||
}
|
||||
|
||||
export class Item {
|
||||
export class Item implements BaseItem {
|
||||
public url: string;
|
||||
|
||||
public usable: string;
|
||||
@ -43,6 +57,8 @@ export class Item {
|
||||
|
||||
public usableReferrer: string;
|
||||
|
||||
public idx: number;
|
||||
|
||||
constructor(raw: any, options?: any) {
|
||||
Object.assign(this, raw);
|
||||
OPTIONPROPS.forEach(maybeAssign.bind(this, options || {}));
|
||||
|
@ -29,8 +29,6 @@ const SAVEDPROPS = [
|
||||
"serverName",
|
||||
// other options
|
||||
"private",
|
||||
"fromMetalink",
|
||||
"cleanRequest",
|
||||
// db
|
||||
"manId",
|
||||
"dbId",
|
||||
|
@ -10,10 +10,24 @@ import { donate, openPrefs, openUrls } from "./windowutils";
|
||||
import { filters, FAST, Filter } from "./filters";
|
||||
import { WindowStateTracker } from "./windowstatetracker";
|
||||
import { windows } from "./browser";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseItem } from "./item";
|
||||
|
||||
interface BaseMatchedItem extends BaseItem {
|
||||
matched?: string | null;
|
||||
prevMatched?: string | null;
|
||||
}
|
||||
|
||||
function computeSelection(filters: any[], items: any[], onlyFast: boolean) {
|
||||
let ws = items.map((item: any, idx: number) => {
|
||||
export interface ItemDelta {
|
||||
idx: number;
|
||||
matched?: string | null;
|
||||
}
|
||||
|
||||
function computeSelection(
|
||||
filters: Filter[],
|
||||
items: BaseMatchedItem[],
|
||||
onlyFast: boolean): ItemDelta[] {
|
||||
let ws = items.map((item, idx: number) => {
|
||||
item.idx = idx;
|
||||
const {matched = null} = item;
|
||||
item.prevMatched = matched;
|
||||
@ -23,9 +37,15 @@ function computeSelection(filters: any[], items: any[], onlyFast: boolean) {
|
||||
for (const filter of filters) {
|
||||
ws = ws.filter(item => {
|
||||
if (filter.matchItem(item)) {
|
||||
item.matched = filter.id === FAST ?
|
||||
"fast" :
|
||||
(onlyFast ? null : filter.id);
|
||||
if (filter.id === FAST) {
|
||||
item.matched = "fast";
|
||||
}
|
||||
else if (!onlyFast && typeof filter.id === "string") {
|
||||
item.matched = filter.id;
|
||||
}
|
||||
else {
|
||||
item.matched = null;
|
||||
}
|
||||
}
|
||||
return !item.matched;
|
||||
});
|
||||
@ -41,6 +61,9 @@ function computeSelection(filters: any[], items: any[], onlyFast: boolean) {
|
||||
function *computeActiveFiltersGen(
|
||||
filters: Filter[], activeOverrides: Map<string, boolean>) {
|
||||
for (const filter of filters) {
|
||||
if (typeof filter.id !== "string") {
|
||||
continue;
|
||||
}
|
||||
const override = activeOverrides.get(filter.id);
|
||||
if (typeof override === "boolean") {
|
||||
if (override) {
|
||||
@ -59,11 +82,11 @@ function computeActiveFilters(
|
||||
return Array.from(computeActiveFiltersGen(filters, activeOverrides));
|
||||
}
|
||||
|
||||
function filtersToDescs(filters: any[]) {
|
||||
function filtersToDescs(filters: Filter[]) {
|
||||
return filters.map(f => f.descriptor);
|
||||
}
|
||||
|
||||
export async function select(links: any[], media: any[]) {
|
||||
export async function select(links: BaseItem[], media: BaseItem[]) {
|
||||
const fm = await filters();
|
||||
const tracker = new WindowStateTracker("select", {
|
||||
minWidth: 700,
|
||||
@ -85,7 +108,7 @@ export async function select(links: any[], media: any[]) {
|
||||
tracker.track(window.id, port);
|
||||
|
||||
const overrides = new Map();
|
||||
let fast: any = null;
|
||||
let fast: Filter | null = null;
|
||||
let onlyFast: false;
|
||||
try {
|
||||
fast = await fm.getFastFilter();
|
||||
@ -95,16 +118,16 @@ export async function select(links: any[], media: any[]) {
|
||||
}
|
||||
|
||||
const sendFilters = function(delta = false) {
|
||||
let {linkFilters, mediaFilters} = fm;
|
||||
const {linkFilters, mediaFilters} = fm;
|
||||
const alink = computeActiveFilters(linkFilters, overrides);
|
||||
const amedia = computeActiveFilters(mediaFilters, overrides);
|
||||
const sactiveFilters = new Set<any>();
|
||||
[alink, amedia].forEach(
|
||||
a => a.forEach(filter => sactiveFilters.add(filter.id)));
|
||||
const activeFilters = Array.from(sactiveFilters);
|
||||
linkFilters = filtersToDescs(linkFilters);
|
||||
mediaFilters = filtersToDescs(mediaFilters);
|
||||
port.post("filters", {linkFilters, mediaFilters, activeFilters});
|
||||
const linkFilterDescs = filtersToDescs(linkFilters);
|
||||
const mediaFilterDescs = filtersToDescs(mediaFilters);
|
||||
port.post("filters", {linkFilterDescs, mediaFilterDescs, activeFilters});
|
||||
|
||||
if (fast) {
|
||||
alink.unshift(fast);
|
||||
@ -128,9 +151,6 @@ export async function select(links: any[], media: any[]) {
|
||||
});
|
||||
|
||||
port.on("queue", (msg: any) => {
|
||||
const selected = new Set<number>(msg.items);
|
||||
const items = (msg.type === "links" ? links : media);
|
||||
msg.items = items.filter((item: any, idx: number) => selected.has(idx));
|
||||
done.resolve(msg);
|
||||
});
|
||||
|
||||
@ -175,7 +195,11 @@ export async function select(links: any[], media: any[]) {
|
||||
sendFilters(false);
|
||||
const type = await Prefs.get("last-type", "links");
|
||||
port.post("items", {type, links, media});
|
||||
const result = await done;
|
||||
const {items, options} = await done;
|
||||
const selectedIndexes = new Set<number>(items);
|
||||
const selectedList = (options.type === "links" ? links : media);
|
||||
const selectedItems = selectedList.filter(
|
||||
(item: BaseItem, idx: number) => selectedIndexes.has(idx));
|
||||
for (const [filter, override] of overrides) {
|
||||
const f = fm.get(filter);
|
||||
if (f) {
|
||||
@ -183,7 +207,7 @@ export async function select(links: any[], media: any[]) {
|
||||
}
|
||||
}
|
||||
await fm.save();
|
||||
return result;
|
||||
return {items: selectedItems, options};
|
||||
}
|
||||
finally {
|
||||
fm.off("changed", sendFilters);
|
||||
|
@ -7,8 +7,10 @@ import { WindowStateTracker } from "./windowstatetracker";
|
||||
import { Promised, timeout } from "./util";
|
||||
import { donate } from "./windowutils";
|
||||
import { windows } from "./browser";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseItem } from "./item";
|
||||
|
||||
export async function single(item: any) {
|
||||
export async function single(item: BaseItem | null) {
|
||||
const tracker = new WindowStateTracker("single", {
|
||||
minWidth: 700,
|
||||
minHeight: 460
|
||||
@ -46,7 +48,9 @@ export async function single(item: any) {
|
||||
donate();
|
||||
});
|
||||
|
||||
port.post("item", item);
|
||||
if (item) {
|
||||
port.post("item", {item});
|
||||
}
|
||||
return await done;
|
||||
}
|
||||
finally {
|
||||
|
@ -14,6 +14,8 @@ import {
|
||||
} from "./tablesymbols";
|
||||
import { InvalidatedSet, UpdateRecord } from "./tableutil";
|
||||
import { addClass, clampUInt, IS_MAC } from "./util";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { TableConfig } from "./config";
|
||||
|
||||
const ROWS_SMALL_UPDATE = 5;
|
||||
const PIXEL_PREC = 5;
|
||||
@ -79,7 +81,7 @@ export class BaseTable extends AbstractTable {
|
||||
|
||||
[COLS]: Columns;
|
||||
|
||||
constructor(elem: any, config: any, version?: number) {
|
||||
constructor(elem: any, config: TableConfig | null, version?: number) {
|
||||
config = (config && config.version === version && config) || {};
|
||||
super();
|
||||
|
||||
@ -121,9 +123,9 @@ export class BaseTable extends AbstractTable {
|
||||
this.makeDOM(config);
|
||||
}
|
||||
|
||||
makeDOM(config: any) {
|
||||
makeDOM(config: TableConfig) {
|
||||
const configColumns = "columns" in config ? config.columns : null;
|
||||
const cols = this[COLS] = new Columns(this, configColumns);
|
||||
const cols = this[COLS] = new Columns(this, configColumns || null);
|
||||
|
||||
const container = document.createElement("div");
|
||||
const thead = document.createElement("div");
|
||||
@ -241,7 +243,7 @@ export class BaseTable extends AbstractTable {
|
||||
return new SelectionRange(firstIdx, lastIdx);
|
||||
}
|
||||
|
||||
get config() {
|
||||
get config(): TableConfig {
|
||||
return {
|
||||
version: this.version,
|
||||
columns: this.columnConfig
|
||||
|
@ -1,14 +1,12 @@
|
||||
"use strict";
|
||||
// License: MIT
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { TableEvents } from "./tableevents";
|
||||
import {addClass, debounce, sum} from "./util";
|
||||
import {EventEmitter} from "./events";
|
||||
import {APOOL} from "./animationpool";
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
// License: MIT
|
||||
import { ColumnConfig, ColumnConfigs } from "./config";
|
||||
|
||||
const PIXLIT_WIDTH = 2;
|
||||
const MIN_COL_WIDTH = 16;
|
||||
@ -55,8 +53,7 @@ export class Column extends EventEmitter {
|
||||
columns: Columns,
|
||||
col: HTMLTableHeaderCellElement,
|
||||
id: number,
|
||||
config: any) {
|
||||
config = config || {};
|
||||
config: ColumnConfig | null) {
|
||||
super();
|
||||
this.columns = columns;
|
||||
this.elem = col;
|
||||
@ -89,7 +86,7 @@ export class Column extends EventEmitter {
|
||||
|
||||
this.elem.appendChild(containerElem);
|
||||
|
||||
if ("visible" in config) {
|
||||
if (config) {
|
||||
this.visible = config.visible;
|
||||
}
|
||||
this.initWidths(config);
|
||||
@ -148,18 +145,18 @@ export class Column extends EventEmitter {
|
||||
return Math.max(0, this.currentWidth - this.minWidth);
|
||||
}
|
||||
|
||||
get config() {
|
||||
get config(): ColumnConfig {
|
||||
return {
|
||||
visible: this.visible,
|
||||
width: this.currentWidth,
|
||||
};
|
||||
}
|
||||
|
||||
initWidths(config: any) {
|
||||
initWidths(config: ColumnConfig | null) {
|
||||
const style = getComputedStyle(this.elem, null);
|
||||
this.minWidth = toPixel(style.getPropertyValue("min-width"), MIN_COL_WIDTH);
|
||||
this.maxWidth = toPixel(style.getPropertyValue("max-width"), 0);
|
||||
const width = config.width || this.baseWidth;
|
||||
const width = (config && config.width) || this.baseWidth;
|
||||
this.setWidth(width);
|
||||
}
|
||||
|
||||
@ -236,7 +233,7 @@ export class Columns extends EventEmitter {
|
||||
|
||||
public visible: Column[];
|
||||
|
||||
constructor(table: any, config: any) {
|
||||
constructor(table: any, config: ColumnConfigs | null) {
|
||||
config = config || {};
|
||||
super();
|
||||
this.table = table;
|
||||
@ -247,7 +244,9 @@ export class Columns extends EventEmitter {
|
||||
this.named = new Map<string, Column>();
|
||||
this.cols = Array.from(table.elem.querySelectorAll("th")).
|
||||
map((colEl: HTMLTableHeaderCellElement, colid: number) => {
|
||||
const columnConfig = colEl.id in config ? config[colEl.id] : null;
|
||||
const columnConfig = config && colEl.id in config ?
|
||||
config[colEl.id] :
|
||||
null;
|
||||
const col = new Column(this, colEl, colid, columnConfig);
|
||||
col.on("gripmoved", this.gripmoved);
|
||||
this.named.set(colEl.id, col);
|
||||
@ -261,7 +260,7 @@ export class Columns extends EventEmitter {
|
||||
Object.seal(this);
|
||||
}
|
||||
|
||||
get config() {
|
||||
get config(): ColumnConfigs {
|
||||
const rv: any = {};
|
||||
for (const c of this.cols) {
|
||||
rv[c.elem.id] = c.config;
|
||||
|
13
uikit/lib/config.ts
Normal file
13
uikit/lib/config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
// License: MIT
|
||||
|
||||
export interface ColumnConfig {
|
||||
visible: boolean;
|
||||
width: number;
|
||||
}
|
||||
export type ColumnConfigs ={ [name: string]: ColumnConfig };
|
||||
|
||||
export interface TableConfig {
|
||||
version?: number;
|
||||
columns?: ColumnConfigs;
|
||||
}
|
@ -33,6 +33,14 @@ export interface MenuPosition {
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
interface MenuOptions {
|
||||
disabled?: string;
|
||||
allowClick?: string;
|
||||
icon?: string;
|
||||
key?: string;
|
||||
autoHide?: string;
|
||||
}
|
||||
|
||||
export class MenuItemBase {
|
||||
public readonly owner: ContextMenu;
|
||||
|
||||
@ -44,7 +52,7 @@ export class MenuItemBase {
|
||||
|
||||
public readonly key: string;
|
||||
|
||||
public readonly autohide: boolean;
|
||||
public readonly autoHide: boolean;
|
||||
|
||||
public readonly elem: HTMLLIElement;
|
||||
|
||||
@ -54,18 +62,16 @@ export class MenuItemBase {
|
||||
|
||||
public readonly keyElem: HTMLSpanElement;
|
||||
|
||||
constructor(owner: ContextMenu, id = "", text = "", {
|
||||
key = "", icon = "", autohide = Object()
|
||||
}) {
|
||||
constructor(owner: ContextMenu, id = "", text = "", options: MenuOptions) {
|
||||
this.owner = owner;
|
||||
if (!id) {
|
||||
id = `contextmenu-${++ids}`;
|
||||
}
|
||||
this.id = id;
|
||||
this.text = text || "";
|
||||
this.icon = icon || "";
|
||||
this.key = key || "";
|
||||
this.autohide = autohide !== "false" && autohide !== false;
|
||||
this.icon = options.icon || "";
|
||||
this.key = options.key || "";
|
||||
this.autoHide = options.autoHide !== "false";
|
||||
|
||||
this.elem = document.createElement("li");
|
||||
this.elem.id = this.id;
|
||||
@ -96,13 +102,14 @@ export class MenuItemBase {
|
||||
}
|
||||
|
||||
export class MenuItem extends MenuItemBase {
|
||||
constructor(owner: ContextMenu, id = "", text = "", options: any = {}) {
|
||||
constructor(
|
||||
owner: ContextMenu, id = "", text = "", options: MenuOptions = {}) {
|
||||
options = options || {};
|
||||
super(owner, id, text, options);
|
||||
this.disabled = !!options.disabled;
|
||||
this.disabled = options.disabled === "true";
|
||||
this.elem.setAttribute("aria-role", "menuitem");
|
||||
this.elem.addEventListener(
|
||||
"click", () => this.owner.emit("clicked", this.id, this.autohide));
|
||||
"click", () => this.owner.emit("clicked", this.id, this.autoHide));
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
@ -132,7 +139,8 @@ export class SubMenuItem extends MenuItemBase {
|
||||
|
||||
public readonly expandElem: HTMLSpanElement;
|
||||
|
||||
constructor(owner: ContextMenu, id = "", text = "", options: any = {}) {
|
||||
constructor(
|
||||
owner: ContextMenu, id = "", text = "", options: MenuOptions = {}) {
|
||||
super(owner, id, text, options);
|
||||
this.elem.setAttribute("aria-role", "menuitem");
|
||||
this.elem.setAttribute("aria-haspopup", "true");
|
||||
@ -145,8 +153,8 @@ export class SubMenuItem extends MenuItemBase {
|
||||
this.expandElem.textContent = "►";
|
||||
this.elem.appendChild(this.expandElem);
|
||||
this.elem.addEventListener("click", event => {
|
||||
if (options.allowClick) {
|
||||
this.owner.emit("clicked", this.id, this.autohide);
|
||||
if (options.allowClick === "true") {
|
||||
this.owner.emit("clicked", this.id, this.autoHide);
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
@ -160,7 +168,7 @@ export class SubMenuItem extends MenuItemBase {
|
||||
this.owner.on("showing", () => {
|
||||
this.menu.dismiss();
|
||||
});
|
||||
this.menu.on("clicked", (...args: any) => {
|
||||
this.menu.on("clicked", (...args: any[]) => {
|
||||
this.owner.emit("clicked", ...args);
|
||||
});
|
||||
}
|
||||
@ -215,7 +223,7 @@ export class SubMenuItem extends MenuItemBase {
|
||||
export class ContextMenu extends EventEmitter {
|
||||
id: string;
|
||||
|
||||
items: any[];
|
||||
items: MenuItemBase[];
|
||||
|
||||
itemMap: Map<string, MenuItemBase>;
|
||||
|
||||
@ -223,7 +231,7 @@ export class ContextMenu extends EventEmitter {
|
||||
|
||||
showing: boolean;
|
||||
|
||||
_maybeDismiss: any;
|
||||
_maybeDismiss: (this: Window, ev: MouseEvent) => any;
|
||||
|
||||
constructor(el?: any) {
|
||||
super();
|
||||
@ -348,10 +356,12 @@ export class ContextMenu extends EventEmitter {
|
||||
return this.itemMap.get(id);
|
||||
}
|
||||
|
||||
add(item: MenuItemBase, before: any = "") {
|
||||
add(item: MenuItemBase, before: MenuItemBase | string = "") {
|
||||
let idx = this.items.length;
|
||||
if (before) {
|
||||
before = before.id || before;
|
||||
if (typeof before !== "string") {
|
||||
before = before.id;
|
||||
}
|
||||
const ni = this.items.findIndex(i => i.id === before);
|
||||
if (ni >= 0) {
|
||||
idx = ni;
|
||||
@ -366,8 +376,8 @@ export class ContextMenu extends EventEmitter {
|
||||
this.itemMap.set(item.id, item);
|
||||
}
|
||||
|
||||
remove(item: any) {
|
||||
const id = item.id || item;
|
||||
remove(item: MenuItemBase | string) {
|
||||
const id = typeof item === "string" ? item : item.id;
|
||||
const idx = this.items.findIndex(i => i.id === id);
|
||||
if (idx >= 0) {
|
||||
this.items.splice(idx, 1);
|
||||
|
@ -1,15 +1,20 @@
|
||||
"use strict";
|
||||
// License: MIT
|
||||
|
||||
interface ModalButton {
|
||||
export interface ModalButton {
|
||||
title: string;
|
||||
value: string;
|
||||
default?: boolean;
|
||||
dismiss?: boolean;
|
||||
}
|
||||
|
||||
interface Promised {
|
||||
resolve: Function;
|
||||
reject: Function;
|
||||
}
|
||||
|
||||
export default abstract class ModalDialog {
|
||||
private _showing: any;
|
||||
private _showing: Promised | null;
|
||||
|
||||
private _dismiss: HTMLButtonElement | null;
|
||||
|
||||
@ -107,7 +112,10 @@ export default abstract class ModalDialog {
|
||||
];
|
||||
}
|
||||
|
||||
done(button: any) {
|
||||
done(button: ModalButton) {
|
||||
if (!this._showing) {
|
||||
return;
|
||||
}
|
||||
const value = this.convertValue(button.value);
|
||||
if (button.dismiss) {
|
||||
this._showing.reject(new Error(value));
|
||||
@ -130,7 +138,7 @@ export default abstract class ModalDialog {
|
||||
}
|
||||
|
||||
|
||||
async show() {
|
||||
async show(): Promise<any> {
|
||||
if (this._showing) {
|
||||
throw new Error("Double show");
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class Hover {
|
||||
|
||||
private hovering: boolean;
|
||||
|
||||
private timer: any;
|
||||
private timer: number | null;
|
||||
|
||||
constructor(row: Row) {
|
||||
this.row = row;
|
||||
@ -62,7 +62,7 @@ class Hover {
|
||||
this.elem.addEventListener("mousemove", this.onmove, {passive: true});
|
||||
this.x = evt.clientX;
|
||||
this.y = evt.clientY;
|
||||
this.timer = setTimeout(this.onhover, HOVER_TIME);
|
||||
this.timer = window.setTimeout(this.onhover, HOVER_TIME);
|
||||
}
|
||||
|
||||
onleave() {
|
||||
@ -93,7 +93,7 @@ class Hover {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.timer = setTimeout(this.onhover, HOVER_TIME);
|
||||
this.timer = window.setTimeout(this.onhover, HOVER_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ import {Row} from "./row";
|
||||
import {APOOL} from "./animationpool";
|
||||
import {COLS, ROWCACHE, VISIBLE} from "./tablesymbols";
|
||||
import {ContextMenu, MenuItem} from "./contextmenu";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { TableConfig } from "./config";
|
||||
|
||||
const RESIZE_DEBOUNCE = 500;
|
||||
const SCROLL_DEBOUNCE = 250;
|
||||
@ -19,7 +21,7 @@ const SCROLL_DEBOUNCE = 250;
|
||||
export class TableEvents extends BaseTable {
|
||||
private oldVisibleTop: number;
|
||||
|
||||
constructor(elem: any, config?: any, version?: number) {
|
||||
constructor(elem: any, config: TableConfig | null, version?: number) {
|
||||
super(elem, config, version);
|
||||
const {selection} = this;
|
||||
selection.on("selection-added", this.selectionAdded.bind(this));
|
||||
@ -172,7 +174,7 @@ export class TableEvents extends BaseTable {
|
||||
ctx,
|
||||
id,
|
||||
col.spanElem.textContent || "",
|
||||
{autohide: "false"});
|
||||
{autoHide: "false"});
|
||||
ctx.add(item);
|
||||
item.iconElem.textContent = col.visible ? "✓" : " ";
|
||||
ctx.on(id, async () => {
|
||||
|
@ -8,6 +8,8 @@ import { Row } from "./row";
|
||||
import {APOOL} from "./animationpool";
|
||||
import {ROW_CACHE_SIZE, ROW_REUSE_SIZE} from "./constants";
|
||||
import {clampUInt} from "./util";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseTable } from "./basetable";
|
||||
|
||||
|
||||
export class InvalidatedSet<T> extends Set<T> {
|
||||
@ -70,7 +72,7 @@ export class UpdateRecord {
|
||||
|
||||
bottom: number;
|
||||
|
||||
constructor(table: any, cols: Column[]) {
|
||||
constructor(table: BaseTable, cols: Column[]) {
|
||||
this.rowCount = table.rowCount;
|
||||
this.scrollTop = table.visibleTop;
|
||||
this.rowHeight = table.rowHeight;
|
||||
|
@ -13,14 +13,21 @@ export function addClass(elem: HTMLElement, ...cls: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
interface Timer {
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export function debounce(fn: Function, to: number) {
|
||||
let timer: any;
|
||||
let timer: Timer | null;
|
||||
return function(...args: any[]) {
|
||||
if (timer) {
|
||||
timer.args = args;
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
const {args} = timer;
|
||||
timer = null;
|
||||
try {
|
||||
@ -38,7 +45,7 @@ function sumreduce(p: number, c: number) {
|
||||
return p + c;
|
||||
}
|
||||
|
||||
export function sum(arr: any[]) {
|
||||
export function sum(arr: number[]) {
|
||||
return arr.reduce(sumreduce, 0);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ export class Dropdown extends EventEmitter {
|
||||
|
||||
select: HTMLSelectElement;
|
||||
|
||||
constructor(el: string, options: any[] = []) {
|
||||
constructor(el: string, options: string[] = []) {
|
||||
super();
|
||||
let input = document.querySelector(el);
|
||||
if (!input || !input.parentElement) {
|
||||
|
@ -128,8 +128,8 @@
|
||||
<template id="menufilter-template">
|
||||
<ul>
|
||||
<li id="ctx-menufilter-seperator">-</li>
|
||||
<li id="ctx-menufilter-invert" data-autohide="false">Invert</li>
|
||||
<li id="ctx-menufilter-clear" data-autohide="false">Clear</li>
|
||||
<li id="ctx-menufilter-invert" data-autoHide="false">Invert</li>
|
||||
<li id="ctx-menufilter-clear" data-autoHide="false">Clear</li>
|
||||
<li>-</li>
|
||||
<li id="ctx-menufilter-sort-ascending" data-icon="icon-sort-asc">Sort ascending</li>
|
||||
<li id="ctx-menufilter-sort-descending" data-icon="icon-sort-desc">Sort descending</li>
|
||||
|
@ -41,7 +41,7 @@ export class TextFilter extends ItemFilter {
|
||||
|
||||
private box: HTMLInputElement;
|
||||
|
||||
private timer: any;
|
||||
private timer: number | null;
|
||||
|
||||
private current: string;
|
||||
|
||||
@ -58,7 +58,7 @@ export class TextFilter extends ItemFilter {
|
||||
if (this.timer) {
|
||||
return;
|
||||
}
|
||||
this.timer = setTimeout(() => this.update(), TIMEOUT_SEARCH);
|
||||
this.timer = window.setTimeout(() => this.update(), TIMEOUT_SEARCH);
|
||||
});
|
||||
this.box.addEventListener("keydown", e => {
|
||||
if (e.key !== "Escape") {
|
||||
@ -152,7 +152,7 @@ export class MenuFilter extends ItemFilter {
|
||||
return;
|
||||
}
|
||||
const item = new MenuItem(this.menu, id, text, {
|
||||
autohide: false,
|
||||
autoHide: "false",
|
||||
});
|
||||
item.iconElem.textContent = checked ? "✓" : "";
|
||||
this.items.set(id, {item, callback});
|
||||
@ -202,16 +202,24 @@ export class MenuFilter extends ItemFilter {
|
||||
}
|
||||
}
|
||||
|
||||
type ChainedFunction = (item: DownloadItem) => boolean;
|
||||
|
||||
interface ChainedItem {
|
||||
text: string;
|
||||
fn: ChainedFunction;
|
||||
}
|
||||
|
||||
class FixedMenuFilter extends MenuFilter {
|
||||
collection: FilteredCollection;
|
||||
|
||||
selected: Set<any>;
|
||||
selected: Set<ChainedItem>;
|
||||
|
||||
fixed: Set<any>;
|
||||
fixed: Set<ChainedItem>;
|
||||
|
||||
chain: any;
|
||||
chain: ChainedFunction | null;
|
||||
|
||||
constructor(id: string, collection: FilteredCollection, items: any[]) {
|
||||
constructor(
|
||||
id: string, collection: FilteredCollection, items: ChainedItem[]) {
|
||||
super(id);
|
||||
this.collection = collection;
|
||||
this.selected = new Set();
|
||||
@ -226,7 +234,7 @@ class FixedMenuFilter extends MenuFilter {
|
||||
});
|
||||
}
|
||||
|
||||
toggle(item: MenuItem) {
|
||||
toggle(item: ChainedItem) {
|
||||
if (this.selected.has(item)) {
|
||||
this.selected.delete(item);
|
||||
}
|
||||
@ -241,14 +249,18 @@ class FixedMenuFilter extends MenuFilter {
|
||||
this.collection.removeFilter(this);
|
||||
return;
|
||||
}
|
||||
this.chain = Array.from(this.selected).reduce((prev, curr) => {
|
||||
return (item: DownloadItem) => curr.fn(item) || (prev && prev(item));
|
||||
}, null);
|
||||
this.chain = null;
|
||||
this.chain = Array.from(this.selected).reduce(
|
||||
(prev: ChainedFunction | null, curr) => {
|
||||
return (item: DownloadItem) => {
|
||||
return curr.fn(item) || (prev !== null && prev(item));
|
||||
};
|
||||
}, this.chain);
|
||||
this.collection.addFilter(this);
|
||||
}
|
||||
|
||||
allow(item: DownloadItem) {
|
||||
return this.chain(item);
|
||||
return this.chain !== null && this.chain(item);
|
||||
}
|
||||
|
||||
clear() {
|
||||
@ -341,7 +353,7 @@ export class UrlMenuFilter extends MenuFilter {
|
||||
});
|
||||
}
|
||||
|
||||
toggleRegularFilter(filter: any) {
|
||||
toggleRegularFilter(filter: Filter) {
|
||||
if (this.filters.has(filter)) {
|
||||
this.filters.delete(filter);
|
||||
}
|
||||
|
@ -2,14 +2,18 @@
|
||||
// License: MIT
|
||||
|
||||
import { EventEmitter } from "../../lib/events";
|
||||
import { runtime } from "../../lib/browser";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { runtime, RawPort } from "../../lib/browser";
|
||||
|
||||
const PORT = new class Port extends EventEmitter {
|
||||
port: any;
|
||||
port: RawPort | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.port = runtime.connect(null, { name: "manager" });
|
||||
if (!this.port) {
|
||||
throw new Error("Could not connect");
|
||||
}
|
||||
this.port.onMessage.addListener((msg: any) => {
|
||||
if (typeof msg === "string") {
|
||||
this.emit(msg);
|
||||
@ -23,10 +27,16 @@ const PORT = new class Port extends EventEmitter {
|
||||
}
|
||||
|
||||
post(msg: string, data?: any) {
|
||||
if (!this.port) {
|
||||
return;
|
||||
}
|
||||
this.port.postMessage(Object.assign({msg}, data));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (!this.port) {
|
||||
return;
|
||||
}
|
||||
this.port.disconnect();
|
||||
this.port = null;
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ import "../../lib/util";
|
||||
import { CellTypes } from "../../uikit/lib/constants";
|
||||
import { downloads } from "../../lib/browser";
|
||||
import { $ } from "../winutil";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { TableConfig } from "../../uikit/lib/config";
|
||||
|
||||
const TREE_CONFIG_VERSION = 2;
|
||||
const RUNNING_TIMEOUT = 1000;
|
||||
@ -311,9 +313,9 @@ export class DownloadTable extends VirtualTable {
|
||||
|
||||
public readonly showUrls: ShowUrlsWatcher;
|
||||
|
||||
private runningTimer: any;
|
||||
private runningTimer: number | null;
|
||||
|
||||
private sizesTimer: any;
|
||||
private sizesTimer: number | null;
|
||||
|
||||
private readonly globalStats: Stats;
|
||||
|
||||
@ -329,7 +331,7 @@ export class DownloadTable extends VirtualTable {
|
||||
|
||||
private readonly openFileAction: Broadcaster;
|
||||
|
||||
private readonly openDirectoryAction: any;
|
||||
private readonly openDirectoryAction: Broadcaster;
|
||||
|
||||
private readonly moveTopAction: Broadcaster;
|
||||
|
||||
@ -339,11 +341,11 @@ export class DownloadTable extends VirtualTable {
|
||||
|
||||
private readonly moveBottomAction: Broadcaster;
|
||||
|
||||
private readonly disableSet: Set<any>;
|
||||
private readonly disableSet: Set<Broadcaster>;
|
||||
|
||||
private tooltip: Tooltip | null;
|
||||
|
||||
constructor(treeConfig: any) {
|
||||
constructor(treeConfig: TableConfig | null) {
|
||||
super("#items", treeConfig, TREE_CONFIG_VERSION);
|
||||
|
||||
TEXT_SIZE_UNKNOWM = _("size-unknown");
|
||||
@ -621,7 +623,7 @@ export class DownloadTable extends VirtualTable {
|
||||
filter(e => e.startsWith(prefix)).
|
||||
forEach(e => rem.remove(e));
|
||||
for (const filt of filts.all) {
|
||||
if (filt.id === "deffilter-all") {
|
||||
if (typeof filt.id !== "string" || filt.id === "deffilter-all") {
|
||||
continue;
|
||||
}
|
||||
const mi = new MenuItem(rem, `${prefix}-${filt.id}`, filt.label, {
|
||||
@ -918,7 +920,7 @@ export class DownloadTable extends VirtualTable {
|
||||
}
|
||||
|
||||
const filter = (await filters()).get(id);
|
||||
if (!filter) {
|
||||
if (!filter || typeof filter.id !== "string") {
|
||||
return;
|
||||
}
|
||||
await new RemovalModalDialog(
|
||||
@ -966,7 +968,7 @@ export class DownloadTable extends VirtualTable {
|
||||
switch (oldState) {
|
||||
case DownloadState.RUNNING:
|
||||
this.running.delete(item);
|
||||
if (!this.running.size && this.runningTimer) {
|
||||
if (!this.running.size && this.runningTimer && this.sizesTimer) {
|
||||
clearInterval(this.runningTimer);
|
||||
this.runningTimer = null;
|
||||
clearInterval(this.sizesTimer);
|
||||
@ -983,9 +985,9 @@ export class DownloadTable extends VirtualTable {
|
||||
case DownloadState.RUNNING:
|
||||
this.running.add(item);
|
||||
if (!this.runningTimer) {
|
||||
this.runningTimer = setInterval(
|
||||
this.runningTimer = window.setInterval(
|
||||
this.updateRunning.bind(this), RUNNING_TIMEOUT);
|
||||
this.sizesTimer = setInterval(
|
||||
this.sizesTimer = window.setInterval(
|
||||
this.updateSizes.bind(this), SIZES_TIMEOUT);
|
||||
this.updateRunning();
|
||||
this.updateSizes();
|
||||
|
@ -6,7 +6,8 @@ import { Prefs, PrefWatcher } from "../lib/prefs";
|
||||
import { hostToDomain } from "../lib/util";
|
||||
import { filters } from "../lib/filters";
|
||||
import {Limits} from "../lib/manager/limits";
|
||||
import ModalDialog from "../uikit/lib/modal";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import ModalDialog, { ModalButton } from "../uikit/lib/modal";
|
||||
import { TYPE_LINK, TYPE_MEDIA } from "../lib/constants";
|
||||
import { iconForPath, visible } from "../lib/windowutils";
|
||||
import { VirtualTable } from "../uikit/lib/table";
|
||||
@ -138,7 +139,7 @@ class CreateFilterDialog extends ModalDialog {
|
||||
this.label.focus();
|
||||
}
|
||||
|
||||
done(b: any) {
|
||||
done(b: ModalButton) {
|
||||
if (!b || !b.default) {
|
||||
return super.done(b);
|
||||
}
|
||||
@ -210,7 +211,7 @@ class FiltersUI extends VirtualTable {
|
||||
ignoreNext: boolean;
|
||||
|
||||
constructor() {
|
||||
super("#filters");
|
||||
super("#filters", null);
|
||||
this.filters = [];
|
||||
this.icons = new Icons($("#icons"));
|
||||
const filter: any = null;
|
||||
@ -403,7 +404,7 @@ class LimitsUI extends VirtualTable {
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super("#limits");
|
||||
super("#limits", null);
|
||||
this.limits = [];
|
||||
Limits.on("changed", () => {
|
||||
this.limits = Array.from(Limits);
|
||||
|
@ -15,10 +15,17 @@ import { Icons } from "./icons";
|
||||
import { sort, naturalCaseCompare } from "../lib/sorting";
|
||||
import { hookButton } from "../lib/manager/renamer";
|
||||
import { CellTypes } from "../uikit/lib/constants";
|
||||
import { runtime } from "../lib/browser";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { runtime, RawPort } from "../lib/browser";
|
||||
import { $ } from "./winutil";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { BaseItem } from "../lib/item";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ItemDelta } from "../lib/select";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { TableConfig } from "../uikit/lib/config";
|
||||
|
||||
const PORT = runtime.connect(null, { name: "select" });
|
||||
const PORT: RawPort = runtime.connect(null, { name: "select" });
|
||||
|
||||
const TREE_CONFIG_VERSION = 1;
|
||||
|
||||
@ -37,7 +44,11 @@ let Mask: Dropdown;
|
||||
let FastFilter: Dropdown;
|
||||
|
||||
|
||||
type DELTAS = {deltaLinks: any[]; deltaMedia: any[]};
|
||||
type DELTAS = {deltaLinks: ItemDelta[]; deltaMedia: ItemDelta[]};
|
||||
|
||||
interface BaseMatchedItem extends BaseItem {
|
||||
matched?: string | null;
|
||||
}
|
||||
|
||||
function cleaErrors() {
|
||||
const not = $("#notification");
|
||||
@ -46,7 +57,7 @@ function cleaErrors() {
|
||||
}
|
||||
|
||||
|
||||
function matched(item: any) {
|
||||
function matched(item: BaseMatchedItem) {
|
||||
return item && item.matched && item.matched !== "unmanual";
|
||||
}
|
||||
|
||||
@ -113,18 +124,20 @@ class CheckClasser extends Map<string, string> {
|
||||
}
|
||||
}
|
||||
|
||||
type KeyFn = (item: BaseMatchedItem) => any;
|
||||
|
||||
class SelectionTable extends VirtualTable {
|
||||
checkClasser: CheckClasser;
|
||||
|
||||
icons: Icons;
|
||||
|
||||
links: any[];
|
||||
links: BaseMatchedItem[];
|
||||
|
||||
media: any[];
|
||||
media: BaseMatchedItem[];
|
||||
|
||||
type: string;
|
||||
|
||||
items: any[];
|
||||
items: BaseMatchedItem[];
|
||||
|
||||
status: HTMLElement;
|
||||
|
||||
@ -142,9 +155,11 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
sortasc: boolean;
|
||||
|
||||
keyfns: Map<string, (item: any) => any>;
|
||||
keyfns: Map<string, KeyFn>;
|
||||
|
||||
constructor(treeConfig: any, type: string, links: any[], media: any[]) {
|
||||
constructor(
|
||||
treeConfig: TableConfig | null, type: string,
|
||||
links: BaseMatchedItem[], media: BaseMatchedItem[]) {
|
||||
if (type === "links" && !links.length) {
|
||||
type = "media";
|
||||
}
|
||||
@ -187,7 +202,7 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
this.sortcol = null;
|
||||
this.sortasc = true;
|
||||
this.keyfns = new Map([
|
||||
this.keyfns = new Map<string, KeyFn>([
|
||||
["colDownload", item => item.usable],
|
||||
["colTitle", item => [item.title, item.usable]],
|
||||
["colDescription", item => [item.description, item.usable]],
|
||||
@ -270,7 +285,7 @@ class SelectionTable extends VirtualTable {
|
||||
oldmask = "";
|
||||
break;
|
||||
}
|
||||
oldmask = m;
|
||||
oldmask = m || oldmask;
|
||||
}
|
||||
try {
|
||||
Keys.suppressed = true;
|
||||
@ -374,7 +389,7 @@ class SelectionTable extends VirtualTable {
|
||||
});
|
||||
}
|
||||
|
||||
applyDeltaTo(delta: any[], items: any[]) {
|
||||
applyDeltaTo(delta: ItemDelta[], items: BaseMatchedItem[]) {
|
||||
const active = items === this.items;
|
||||
for (const d of delta) {
|
||||
const {idx = -1, matched = null} = d;
|
||||
@ -432,7 +447,7 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
getRowClasses(rowid: number) {
|
||||
const item = this.items[rowid];
|
||||
if (!item || !matched(item)) {
|
||||
if (!item || !matched(item) || !item.matched) {
|
||||
return null;
|
||||
}
|
||||
return ["filtered", this.checkClasser.get(item.matched)];
|
||||
@ -467,7 +482,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getText(prop: string, idx: number) {
|
||||
const item = this.items[idx];
|
||||
const item: any = this.items[idx];
|
||||
if (!item || !(prop in item) || !item[prop]) {
|
||||
return "";
|
||||
}
|
||||
@ -506,7 +521,7 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
getCellCheck(rowid: number, colid: number) {
|
||||
if (colid === COL_CHECK) {
|
||||
return matched(this.items[rowid]);
|
||||
return !!matched(this.items[rowid]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -549,13 +564,15 @@ async function download(paused = false) {
|
||||
}
|
||||
PORT.postMessage({
|
||||
msg: "queue",
|
||||
type: Table.type,
|
||||
items,
|
||||
options: {
|
||||
type: Table.type,
|
||||
paused,
|
||||
mask,
|
||||
maskOnce: $<HTMLInputElement>("#maskOnceCheck").checked,
|
||||
fast: FastFilter.value,
|
||||
fastOnce: $<HTMLInputElement>("#fastOnceCheck").checked,
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
@ -567,17 +584,17 @@ async function download(paused = false) {
|
||||
}
|
||||
|
||||
class Filter {
|
||||
active: any;
|
||||
|
||||
container: any;
|
||||
|
||||
elem: HTMLLabelElement;
|
||||
|
||||
label: any;
|
||||
active: boolean;
|
||||
|
||||
checkElem: HTMLInputElement;
|
||||
|
||||
id: any;
|
||||
container: HTMLElement;
|
||||
|
||||
elem: HTMLLabelElement;
|
||||
|
||||
id: string;
|
||||
|
||||
label: string;
|
||||
|
||||
constructor(container: HTMLElement, raw: any, active = false) {
|
||||
Object.assign(this, raw);
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
import ModalDialog from "../uikit/lib/modal";
|
||||
import { _, localize } from "../lib/i18n";
|
||||
import { Item } from "../lib/item";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Item, BaseItem } from "../lib/item";
|
||||
import { MASK } from "../lib/recentlist";
|
||||
import { BatchGenerator } from "../lib/batches";
|
||||
import { WindowState } from "./windowstate";
|
||||
@ -16,7 +17,7 @@ import { $ } from "./winutil";
|
||||
|
||||
const PORT = runtime.connect(null, { name: "single" });
|
||||
|
||||
let ITEM: any;
|
||||
let ITEM: BaseItem;
|
||||
let Mask: Dropdown;
|
||||
|
||||
class BatchModalDialog extends ModalDialog {
|
||||
@ -59,7 +60,7 @@ class BatchModalDialog extends ModalDialog {
|
||||
}
|
||||
}
|
||||
|
||||
function setItem(item: any) {
|
||||
function setItem(item: BaseItem) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@ -179,10 +180,12 @@ async function downloadInternal(paused: boolean) {
|
||||
|
||||
PORT.postMessage({
|
||||
msg: "queue",
|
||||
paused,
|
||||
items,
|
||||
options: {
|
||||
paused,
|
||||
mask,
|
||||
maskOnce: $<HTMLInputElement>("#maskOnceCheck").checked,
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@ -204,7 +207,27 @@ async function init() {
|
||||
|
||||
addEventListener("DOMContentLoaded", async function dom() {
|
||||
removeEventListener("DOMContentLoaded", dom);
|
||||
await init();
|
||||
|
||||
const inited = init();
|
||||
PORT.onMessage.addListener(async (msg: any) => {
|
||||
try {
|
||||
switch (msg.msg) {
|
||||
case "item": {
|
||||
await inited;
|
||||
setItem(msg.data.item);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
throw Error("Unhandled message");
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Failed to process message", msg, ex);
|
||||
}
|
||||
});
|
||||
|
||||
await inited;
|
||||
|
||||
$("#btnDownload").addEventListener("click", () => download(false));
|
||||
$("#btnPaused").addEventListener("click", () => download(true));
|
||||
@ -224,23 +247,6 @@ addEventListener("DOMContentLoaded", async function dom() {
|
||||
return true;
|
||||
});
|
||||
|
||||
PORT.onMessage.addListener((msg: any) => {
|
||||
try {
|
||||
switch (msg.msg) {
|
||||
case "item": {
|
||||
setItem(msg.data);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
throw Error("Unhandled message");
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Failed to process message", msg, ex);
|
||||
}
|
||||
});
|
||||
|
||||
hookButton($("#maskButton"));
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { RawPort } from "../lib/browser";
|
||||
|
||||
// License: MIT
|
||||
|
||||
export class WindowState {
|
||||
private readonly port: any;
|
||||
private readonly port: RawPort;
|
||||
|
||||
constructor(port: any) {
|
||||
constructor(port: RawPort) {
|
||||
this.port = port;
|
||||
this.update = this.update.bind(this);
|
||||
addEventListener("resize", this.update);
|
||||
|
Loading…
x
Reference in New Issue
Block a user