Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
2ccc12de90 | |||
28e6866db8 | |||
7185964649 | |||
cd1005823d | |||
0afceb9850 | |||
e6dc205b9d | |||
8e473c778b | |||
7a71ae5f37 | |||
d04f3db22f | |||
adc6b9dbb2 | |||
e8f09c80f3 | |||
46c4e66558 | |||
a8ea416a67 | |||
320c1ddafa | |||
4c576ba720 | |||
7b58779f9e | |||
45d835fe19 | |||
2d2826d192 | |||
4c77ad0f1f | |||
4d72ac4534 | |||
cdda0835d8 | |||
78e91304eb | |||
0702631003 | |||
c45bf671fb | |||
33a3e275fc | |||
369514f155 | |||
494479ce1a | |||
98ebb160f9 | |||
d1cc406f05 | |||
687b6e1aa9 | |||
c960d48b72 | |||
c8c7506efc | |||
91edcee28c | |||
a425a786ef | |||
f023351acc | |||
c8610eee29 | |||
f7a70ec2ea | |||
69d8ffe8a5 | |||
71240ec1e8 | |||
a6f3c7a647 | |||
eab9631a11 | |||
9e29db911c | |||
8d1040115a | |||
3551545eae | |||
01001dc7b2 | |||
b4e6ab80d2 |
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,6 +7,11 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
@ -23,16 +28,6 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
15
LICENSE.md
15
LICENSE.md
@ -30,7 +30,7 @@ THE SOFTWARE.
|
||||
## DownThemAll! uikit
|
||||
|
||||
Copyright © 2016-2019 by Nils Maier
|
||||
The uikit libraries and assets are licened under the MIT license.
|
||||
The uikit libraries and assets are licensed under the MIT license.
|
||||
|
||||
## DownThemAll! interface (.html, .css)
|
||||
|
||||
@ -41,7 +41,8 @@ Licensed under GPL2.0; see [LICENSE.gpl-2.0.txt](LICENSE.gpl-2.0.txt).
|
||||
## DownThemAll! icons, icon-font and graphic assets
|
||||
|
||||
Copyright (C) 2012-2019 by Nils Maier
|
||||
Licensed under Creative Commons Attribution-ShareAlike 4.0 International
|
||||
Licensed under Creative Commons Attribution-ShareAlike 4.0 International.
|
||||
|
||||
The icon font contains icons from Font Awesome.
|
||||
|
||||
See: https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
@ -54,21 +55,25 @@ Copyright © 2010-2019 by Nils Maier, Stefano Verna.
|
||||
The DownThemAll! name and logo cannot be used without explicit permission
|
||||
in any derivative work, except in credits and license-related notices.
|
||||
|
||||
Using the DownThemAll! logo in personal non-distributed non-commerical
|
||||
Using the DownThemAll! logo in personal non-distributed non-commercial
|
||||
modifications of the software and forks is permitted without explicit
|
||||
permission.
|
||||
|
||||
Distributing official DownThemAll! releases without any modifications is allowed without explicit permission.
|
||||
|
||||
## Font Awesome
|
||||
|
||||
Copyright (C) 2016 by Dave Gandy
|
||||
|
||||
License: SIL ()
|
||||
|
||||
Homepage: http://fortawesome.github.com/Font-Awesome/
|
||||
|
||||
## webextension-polyfill
|
||||
|
||||
Lcensed under the Mozilla Public License 2.0.
|
||||
Licensed under the Mozilla Public License 2.0.
|
||||
|
||||
## PSL (public-suffix-list)
|
||||
|
||||
The list itself is licensed under the Mozilla Public License 2.0.
|
||||
The javascript library accessing it is licensed under the MIT license.
|
||||
The javascript library accessing it is licensed under the MIT license.
|
||||
|
@ -36,7 +36,9 @@ You will want to `yarn` the development dependencies such as webpack first.
|
||||
Afterwards there is two important commands to run
|
||||
|
||||
* `yarn watch` - This will run the webpack bundler in watch mode, updating bundles as you change the source.
|
||||
* `yarn webext` - This will run the WebExtension in a development profile using the [`web-ext` tool from mozilla](https://www.npmjs.com/package/web-ext) (which you need to install separately).
|
||||
* `yarn webext` - This will run the WebExtension in a development profile using the [`web-ext` tool from mozilla](https://www.npmjs.com/package/web-ext) (which you need to install separately). This will use the directory `../dtalite.p` to keep a development profile. You might need to create this directory before you use this command first.
|
||||
|
||||
Please note: You have to run `yarn watch` (at least once) as it builds the actual script bundles.
|
||||
|
||||
Alternative, you can also `yarn build`, which then builds an *unsigned* zip that you can then install permanently in a browser that does not enforce signing (i.e. Nightly or the Unbranded Firefox).
|
||||
|
||||
|
4
TODO.md
4
TODO.md
@ -15,10 +15,6 @@ Planned for later.
|
||||
* Add downloads
|
||||
* Chrome support
|
||||
* vtable perf: cache column widths
|
||||
* Localizations
|
||||
* Settle on system
|
||||
* Do the de-locale
|
||||
* Enagage translators
|
||||
* Download options
|
||||
* This is a bit more limited, as we cannot modify options of downloads that have been started (and paused) or that are done.
|
||||
|
||||
|
@ -1,20 +1,33 @@
|
||||
# Translations
|
||||
|
||||
Right now we did not standardize on a tool to translate, so feel free to whip our your favorite text edits, JSON editor, special translation tool, what have you.
|
||||
Right now we did not standardize on a tool/website/community use for translations
|
||||
|
||||
To make a translation of DownThemAll! in your language, please:
|
||||
## Website-based Translation
|
||||
|
||||
Please go to [https://downthemall.github.io/translate/](https://downthemall.github.io/translate/) for a "good enough" tool to translate DownThemAll! for now. It will load the English locale as a base automatically.
|
||||
|
||||
Then you can translate (your progress will be saved in the browser). Once done, you can Download the `messages.json` and test it or submit it for inclusion.
|
||||
|
||||
You can also import your or other people's existing translations to modify. This will overwrite any progress you made so far, tho.
|
||||
|
||||
## Manual Translation
|
||||
|
||||
* Get the [`en/messages.json`](https://github.com/downthemall/downthemall/raw/master/_locales/en/messages.json) as a base.
|
||||
* Translate the `"message"` items in that file only.
|
||||
* Do not translate anything other.
|
||||
* Translate the `"message"` items in that file only. Whip our your favorite text editor, JSON editor, special translation tool, what have you.
|
||||
* Do not translate anything besides the "message" elements. Pay attention to the descriptions.
|
||||
* Do not remove anything.
|
||||
* Do not translate `$PLACEHOLDERS$`. Placeholders should appear in your translation with the same spelling and all uppercase.
|
||||
They will be relaced at runtime with actual values.
|
||||
* Make sure you save the file in an "utf-8" encoding. If you need double quotes, you need to escape the quotes with a backslash, e.g. `"some \"quoted\" text"`
|
||||
* You should translate all strings. If you want to skip a string, set it to an empty `""` string. DTA will then use the English string.
|
||||
* Once you are at a point you want to test things:
|
||||
* Go to the DownThemAll! Preferences where you will find a "Load custom translation" button.
|
||||
* Select your translated `messages.json`. (it doesn't have to be named exactly like that, but should have a `.json` extension)
|
||||
* Make sure you save the file in an "utf-8" encoding. If you need double quotes, you need to escape the quotes with a backslash, e.g. `"some \"quoted\" text"`
|
||||
* You should translate all strings. If you want to skip a string, set it to an empty `""` string. DTA will then use the English string.
|
||||
|
||||
## Testing Your Translation
|
||||
|
||||
* Go to the DownThemAll! Preferences where you will find a "Load custom translation" button.
|
||||
* Select your translated `messages.json`. (it doesn't have to be named exactly like that, but should have a `.json` extension)
|
||||
* If everything was OK, you will be asked to reload the extension (this will only reload DTA not the entire browser).
|
||||
* See your strings in action once you reloaded DTA (either by answering OK when asked, or disable/enable the extension manually or restart your browser).
|
||||
* If you're happy with the result and would like to contribute it back, you can either file a full Pull Request, or just file an issue and post a link to e.g. a [gist](https://gist.github.com/) or paste the translation in the issue text.
|
||||
* See your strings in action once you reloaded DTA (either by answering OK when asked, or disable/enable the extension manually or restart your browser).
|
||||
|
||||
## Submitting Your Translation
|
||||
|
||||
If you're happy with the result and would like to contribute it back, you can either file a full Pull Request, or just file an issue and post a link to e.g. a [gist](https://gist.github.com/) or paste the translation in the issue text.
|
||||
|
1170
_locales/de/messages.json
Normal file
1170
_locales/de/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1170
_locales/es/messages.json
Normal file
1170
_locales/es/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/et/messages.json
Normal file
1170
_locales/et/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1178
_locales/fr/messages.json
Normal file
1178
_locales/fr/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/lt/messages.json
Normal file
1170
_locales/lt/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/nl/messages.json
Normal file
1170
_locales/nl/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/pl/messages.json
Executable file
1170
_locales/pl/messages.json
Executable file
File diff suppressed because it is too large
Load Diff
1160
_locales/pt/messages.json
Normal file
1160
_locales/pt/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/zh-CN/messages.json
Normal file
1170
_locales/zh-CN/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@
|
||||
},
|
||||
"deffilter-aud": {
|
||||
"label": "Audio",
|
||||
"expr": "/\\.(?:mp3|wav|og(?:g|a)|flac|midi?|rm|aac|wma|mka|ape)$/i",
|
||||
"expr": "/\\.(?:mp3|wav|og(?:g|a)|flac|midi?|rm|aac|wma|mka|ape|opus)$/i",
|
||||
"type": 1,
|
||||
"active": false,
|
||||
"icon": "mp3"
|
||||
@ -35,7 +35,7 @@
|
||||
},
|
||||
"deffilter-img": {
|
||||
"label": "Images",
|
||||
"expr": "/\\.(?:jp(?:e?g|e|2)|gif|png|tiff?|bmp|ico)$/i",
|
||||
"expr": "/\\.(?:jp(?:e?g|e|2)|gif|png|tiff?|bmp|ico|heic|heif|webp|jxr|wdp|dng|cr2|arw)$/i",
|
||||
"type": 3,
|
||||
"active": false,
|
||||
"icon": "jpg"
|
||||
@ -63,7 +63,7 @@
|
||||
},
|
||||
"deffilter-vid": {
|
||||
"label": "Videos",
|
||||
"expr": "/\\.(?:mpeg|ra?m|avi|mp(?:g|e|4)|mov|divx|asf|qt|wmv|m\\dv|rv|vob|asx|ogm|ogv|webm|flv|mkv)$/i",
|
||||
"expr": "/\\.(?:mpeg|ra?m|avi|mp(?:g|e|4)|mov|divx|asf|qt|wmv|m\\dv|rv|vob|asx|ogm|ogv|webm|flv|mkv|f4v|m4v)$/i",
|
||||
"type": 3,
|
||||
"active": true,
|
||||
"icon": "mkv"
|
||||
|
@ -80,7 +80,11 @@
|
||||
"tif",
|
||||
"tiff",
|
||||
"wmf",
|
||||
"webp"
|
||||
"webp",
|
||||
"heic",
|
||||
"heif",
|
||||
"jxr",
|
||||
"wdp"
|
||||
],
|
||||
"video": [
|
||||
"3g2",
|
||||
|
@ -9,7 +9,7 @@
|
||||
"hide-context": false,
|
||||
"conflict-action": "uniquify",
|
||||
"nagging": 0,
|
||||
"nagging-next": 6,
|
||||
"nagging-next": 7,
|
||||
"tooltip": true,
|
||||
"show-urls": false,
|
||||
"remove-missing-on-init": false,
|
||||
|
@ -128,7 +128,6 @@ export const API = new class APIImpl {
|
||||
return false;
|
||||
}
|
||||
const {items, options} = await select(links, media);
|
||||
console.log(items, options);
|
||||
return this.regularInternal(items, options);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Tab,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MenuClickInfo
|
||||
MenuClickInfo,
|
||||
} from "./browser";
|
||||
import { Bus } from "./bus";
|
||||
import { filterInSitu } from "./util";
|
||||
@ -389,7 +389,10 @@ locale.then(() => {
|
||||
}
|
||||
|
||||
async enumulate(action: string) {
|
||||
const tab = await tabs.query({active: true});
|
||||
const tab = await tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
if (!tab || !tab.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -123,9 +123,13 @@ async function loadRawLocales() {
|
||||
const langs = new Set<string>(["en"]);
|
||||
const ui = (browser.i18n || chrome.i18n).getUILanguage();
|
||||
langs.add(ui);
|
||||
|
||||
// Try the base too
|
||||
if (ui.includes("-")) {
|
||||
// Try the base too
|
||||
langs.add(ui.split(/[_-]+/)[0]);
|
||||
langs.add(ui.split(/[-]+/)[0]);
|
||||
}
|
||||
else if (ui.includes("_")) {
|
||||
langs.add(ui.split(/[_]+/)[0]);
|
||||
}
|
||||
|
||||
const fetched = await Promise.all(Array.from(langs, fetchLanguage));
|
||||
@ -147,7 +151,6 @@ async function load(): Promise<Localization> {
|
||||
}
|
||||
|
||||
const custom = localStorage.getItem(CUSTOM_KEY);
|
||||
console.log("custom", custom);
|
||||
if (custom) {
|
||||
try {
|
||||
valid.push(JSON.parse(custom));
|
||||
|
@ -12,13 +12,20 @@ const DEFAULTS = {
|
||||
message: "message",
|
||||
};
|
||||
|
||||
const TIMEOUT = 4000;
|
||||
|
||||
let gid = 1;
|
||||
|
||||
export class Notification extends EventEmitter {
|
||||
private notification: any;
|
||||
|
||||
private readonly generated: boolean;
|
||||
|
||||
constructor(id: string | null, options = {}) {
|
||||
super();
|
||||
|
||||
id = id || "DownThemAll-notification";
|
||||
this.generated = !id;
|
||||
id = id || `DownThemAll-notification${++gid}`;
|
||||
if (typeof options === "string") {
|
||||
options = {message: options};
|
||||
}
|
||||
@ -39,11 +46,16 @@ export class Notification extends EventEmitter {
|
||||
opened(notification: any) {
|
||||
this.notification = notification;
|
||||
this.emit("opened", this);
|
||||
if (this.generated) {
|
||||
setTimeout(() => {
|
||||
notifications.clear(notification);
|
||||
}, TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
clicked(notification: any, button?: number) {
|
||||
// We can only be clicked, when we were opened, at which point the
|
||||
// notification id is availablfalse
|
||||
// notification id is available
|
||||
if (notification !== this.notification) {
|
||||
return;
|
||||
}
|
||||
@ -52,6 +64,7 @@ export class Notification extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.emit("clicked", this);
|
||||
console.log("clicked", notification);
|
||||
}
|
||||
|
||||
async closed(notification: any) {
|
||||
|
@ -50,7 +50,7 @@ function computeSelection(
|
||||
return !item.matched;
|
||||
});
|
||||
}
|
||||
return items.filter(item => item.prevMatched !== item.matched). map(item => {
|
||||
return items.filter(item => item.prevMatched !== item.matched).map(item => {
|
||||
return {
|
||||
idx: item.idx,
|
||||
matched: item.matched
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "DownThemAll!",
|
||||
"version": "4.0.3",
|
||||
"version": "4.0.6",
|
||||
|
||||
"description": "__MSG_extensionDescription__",
|
||||
"homepage_url": "https://downthemall.org/",
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
"default_locale": "en",
|
||||
|
||||
"content_security_policy": "script-src 'self'; style-src 'self' 'unsafe-inline'; default-src 'self'",
|
||||
|
||||
"icons": {
|
||||
"16": "style/icon16.png",
|
||||
"32": "style/icon32.png",
|
||||
|
@ -22,13 +22,13 @@
|
||||
"@typescript-eslint/eslint-plugin": "^2.0.0",
|
||||
"@typescript-eslint/parser": "^2.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint": "^6.2.2",
|
||||
"mocha": "^6.2.0",
|
||||
"ts-loader": "^6.0.4",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.39.2",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack": "^4.39.3",
|
||||
"webpack-cli": "^3.3.7",
|
||||
"xregexp": "^4.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -49,7 +49,7 @@ p.example {
|
||||
}
|
||||
|
||||
#options > * {
|
||||
margin: 0;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#options > input {
|
||||
|
@ -10,7 +10,7 @@ const MENU_OPEN_BOUNCE = 500;
|
||||
|
||||
let ids = 0;
|
||||
|
||||
const Keys = new Map([
|
||||
export const Keys = new Map([
|
||||
["ACCEL", IS_MAC ? "⌘" : "Ctrl"],
|
||||
["CTRL", "Ctrl"],
|
||||
["ALT", IS_MAC ? "⌥" : "Alt"],
|
||||
|
@ -42,6 +42,11 @@ module.exports = {
|
||||
filename: "[name].js"
|
||||
},
|
||||
devtool: "source-map",
|
||||
stats: {
|
||||
hash: true,
|
||||
timings: true,
|
||||
maxModules: 2,
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: /node_modules|bundles/
|
||||
},
|
||||
|
22
windows/contextmenu.ts
Normal file
22
windows/contextmenu.ts
Normal file
@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
// License: MIT
|
||||
|
||||
export * from "../uikit/lib/contextmenu";
|
||||
import { Keys } from "../uikit/lib/contextmenu";
|
||||
import { IS_MAC } from "../uikit/lib/util";
|
||||
import { locale, _ } from "../lib/i18n";
|
||||
|
||||
locale.then(() => {
|
||||
Keys.clear();
|
||||
[
|
||||
["ACCEL", IS_MAC ? "⌘" : _("key-ctrl")],
|
||||
["CTRL", _("key-ctrl")],
|
||||
["ALT", IS_MAC ? "⌥" : _("key-alt")],
|
||||
["DELETE", _("key-delete")],
|
||||
["PAGEUP", _("key-pageup")],
|
||||
["PAGEDOWN", _("key-pagedown")],
|
||||
["HOME", _("key-home")],
|
||||
["END", _("key-end")],
|
||||
["SHIFT", "⇧"],
|
||||
].forEach(([k, v]) => Keys.set(k, v));
|
||||
});
|
@ -3,7 +3,7 @@
|
||||
|
||||
import {EventEmitter} from "../lib/events";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ContextMenu } from "../uikit/lib/contextmenu";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { runtime } from "../lib/browser";
|
||||
|
||||
export const Keys = new class extends EventEmitter {
|
||||
|
@ -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-auto-hide="false">Invert</li>
|
||||
<li id="ctx-menufilter-clear" data-auto-hide="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>
|
||||
|
@ -44,7 +44,7 @@ addEventListener("DOMContentLoaded", function dom() {
|
||||
const fullyloaded = Promise.all([LOADED, platformed, tabled, localized]);
|
||||
fullyloaded.then(async () => {
|
||||
const nag = await Prefs.get("nagging", 0);
|
||||
const nagnext = await Prefs.get("nagging-next", 6);
|
||||
const nagnext = await Prefs.get("nagging-next", 7);
|
||||
const next = Math.ceil(Math.log2(Math.max(1, nag)));
|
||||
const el = $("#nagging");
|
||||
const remove = () => {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
MenuItemBase,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MenuPosition,
|
||||
} from "../../uikit/lib/contextmenu";
|
||||
} from "../contextmenu";
|
||||
import {EventEmitter} from "../../lib/events";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import {filters, Matcher, Filter} from "../../lib/filters";
|
||||
@ -340,14 +340,19 @@ export class UrlMenuFilter extends MenuFilter {
|
||||
async populate() {
|
||||
const filts = await filters();
|
||||
for (const i of filts.all.filter(e => e.id !== "deffilter-all")) {
|
||||
this.addItem(i.label, this.toggleRegularFilter.bind(this, i));
|
||||
this.addItem(
|
||||
i.label, this.toggleRegularFilter.bind(this, i), this.filters.has(i));
|
||||
}
|
||||
this.addItem("-");
|
||||
sort(
|
||||
const domains = sort(
|
||||
Array.from(new Set(this.collection.items.map(e => e.domain))),
|
||||
undefined,
|
||||
naturalCaseCompare
|
||||
).forEach(e => {
|
||||
);
|
||||
if (!domains.length) {
|
||||
return;
|
||||
}
|
||||
this.addItem("-");
|
||||
domains.forEach(e => {
|
||||
this.addItem(
|
||||
e, this.toggleDomainFilter.bind(this, e), this.domains.has(e));
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
MenuItem,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
SubMenuItem
|
||||
} from "../../uikit/lib/contextmenu";
|
||||
} from "../contextmenu";
|
||||
import { iconForPath } from "../../lib/windowutils";
|
||||
import { formatSpeed, formatSize, formatTimeDelta } from "../../lib/formatters";
|
||||
import { filters } from "../../lib/filters";
|
||||
|
@ -7,6 +7,10 @@
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 1.5ex;
|
||||
margin-right: 2ex;
|
||||
@ -25,6 +29,7 @@
|
||||
vertical-align: center;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
li.sep {
|
||||
|
@ -65,10 +65,10 @@
|
||||
<label><input type="checkbox" id="pref-remove-missing-on-init"> <span data-i18n="pref-remove-missing-on-init"></span></label>
|
||||
</fieldset>
|
||||
<fieldset id="pref-conflict-action">
|
||||
<legend data-i18="prefs.conflict">When a file exists</legend>
|
||||
<label><input type="radio" name="pref-conflict-action" value="overwrite"> <span>Overwrite</span></label>
|
||||
<label><input type="radio" name="pref-conflict-action" value="uniquify"> <span>Rename</span></label>
|
||||
<!--<label><input type="radio" name="pref-conflict-action" value="prompt"> <span>Prompt</span></label>-->
|
||||
<legend data-i18n="prefs.conflicts">When a file exists</legend>
|
||||
<label><input type="radio" name="pref-conflict-action" value="overwrite"> <span data-i18n="conflict-overwrite">Overwrite</span></label>
|
||||
<label><input type="radio" name="pref-conflict-action" value="uniquify"> <span data-i18n="conflict-rename">Rename</span></label>
|
||||
<!--<label><input type="radio" name="pref-conflict-action" value="prompt"> <span data-i18n="conflict-prompt">Prompt</span></label>-->
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Translations</legend>
|
||||
|
@ -124,7 +124,7 @@ class CreateFilterDialog extends ModalDialog {
|
||||
get buttons() {
|
||||
return [
|
||||
{
|
||||
title: "Create",
|
||||
title: _("create-filter"),
|
||||
value: "ok",
|
||||
default: true
|
||||
},
|
||||
@ -584,7 +584,7 @@ addEventListener("DOMContentLoaded", () => {
|
||||
await Prefs.reset(k);
|
||||
}
|
||||
await ModalDialog.inform(
|
||||
_("information.title"), _("reset-confirmations.done"), _("ok"));
|
||||
_("information.title"), _("reset-layouts.done"), _("ok"));
|
||||
});
|
||||
|
||||
// Filters
|
||||
|
@ -44,7 +44,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<section id="filters" class="collapsable">
|
||||
<h2>Filters</h2>
|
||||
<h2 data-i18n="options-filters"></h2>
|
||||
<div class="filters-container" id="linksFilters"></div>
|
||||
<div class="filters-container" id="mediaFilters"></div>
|
||||
</section>
|
||||
@ -89,7 +89,7 @@
|
||||
<li id="ctx-select-filtered" data-key="ACCEL-KeyF" data-i18n="select-checked">Select Checked</li>
|
||||
<li id="ctx-select-invert" data-key="ACCEL-KeyI" data-i18n="invert-selection">Invert Selection</li>
|
||||
<li>-</li>
|
||||
<li id="ctx-open-selected" data-icon="icon-launch" data-key="ACCEL-KeyO">Open</li>
|
||||
<li id="ctx-open-selected" data-icon="icon-launch" data-key="ACCEL-KeyO" data-i18n="open-link">Open</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="paused-template">
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import { VirtualTable } from "../uikit/lib/table";
|
||||
import ModalDialog from "../uikit/lib/modal";
|
||||
import { ContextMenu } from "../uikit/lib/contextmenu";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { iconForPath } from "../lib/windowutils";
|
||||
import { _, localize } from "../lib/i18n";
|
||||
import { Prefs } from "../lib/prefs";
|
||||
@ -48,6 +48,7 @@ type DELTAS = {deltaLinks: ItemDelta[]; deltaMedia: ItemDelta[]};
|
||||
|
||||
interface BaseMatchedItem extends BaseItem {
|
||||
matched?: string | null;
|
||||
rowid: number;
|
||||
}
|
||||
|
||||
function cleaErrors() {
|
||||
@ -96,8 +97,6 @@ class PausedModalDialog extends ModalDialog {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CheckClasser extends Map<string, string> {
|
||||
gen: IterableIterator<string>;
|
||||
|
||||
@ -126,19 +125,82 @@ class CheckClasser extends Map<string, string> {
|
||||
|
||||
type KeyFn = (item: BaseMatchedItem) => any;
|
||||
|
||||
class ItemCollection {
|
||||
private items: BaseMatchedItem[];
|
||||
|
||||
private indexes: Map<number, BaseMatchedItem>;
|
||||
|
||||
constructor(items: BaseMatchedItem[]) {
|
||||
this.items = items;
|
||||
this.assignRows();
|
||||
this.indexes = new Map(items.map(i => [i.idx, i]));
|
||||
}
|
||||
|
||||
assignRows() {
|
||||
this.items.forEach((item, idx) => item.rowid = idx);
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
get checked() {
|
||||
const rv: number[] = [];
|
||||
this.items.forEach(function (item, idx) {
|
||||
if (item.matched && item.matched !== "unmanual") {
|
||||
rv.push(idx);
|
||||
}
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
|
||||
get checkedIndexes() {
|
||||
const rv: number[] = [];
|
||||
this.items.forEach(function (item) {
|
||||
if (item.matched && item.matched !== "unmanual") {
|
||||
rv.push(item.idx);
|
||||
}
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
at(idx: number) {
|
||||
return this.items[idx];
|
||||
}
|
||||
|
||||
byIndex(idx: number) {
|
||||
return this.indexes.get(idx);
|
||||
}
|
||||
|
||||
sort(keyFn: KeyFn) {
|
||||
sort(this.items, keyFn, naturalCaseCompare);
|
||||
this.assignRows();
|
||||
}
|
||||
|
||||
reverse() {
|
||||
this.items.reverse();
|
||||
this.assignRows();
|
||||
}
|
||||
|
||||
filter(fn: (item: BaseMatchedItem, idx: number) => boolean) {
|
||||
return this.items.filter(fn);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionTable extends VirtualTable {
|
||||
checkClasser: CheckClasser;
|
||||
|
||||
icons: Icons;
|
||||
|
||||
links: BaseMatchedItem[];
|
||||
links: ItemCollection;
|
||||
|
||||
media: BaseMatchedItem[];
|
||||
media: ItemCollection;
|
||||
|
||||
items: ItemCollection;
|
||||
|
||||
type: string;
|
||||
|
||||
items: BaseMatchedItem[];
|
||||
|
||||
status: HTMLElement;
|
||||
|
||||
linksTab: HTMLElement;
|
||||
@ -170,10 +232,10 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
this.checkClasser = new CheckClasser(NUM_FILTER_CLASSES);
|
||||
this.icons = new Icons($("#icons") as HTMLStyleElement);
|
||||
this.links = links;
|
||||
this.media = media;
|
||||
this.links = new ItemCollection(links);
|
||||
this.media = new ItemCollection(media);
|
||||
this.type = type;
|
||||
this.items = (this as any)[type];
|
||||
this.items = type === "links" ? this.links : this.media;
|
||||
|
||||
this.status = $("#statusItems");
|
||||
this.linksTab = $("#linksTab");
|
||||
@ -217,8 +279,8 @@ class SelectionTable extends VirtualTable {
|
||||
if (!keyfn) {
|
||||
return false;
|
||||
}
|
||||
sort(this.links, keyfn, naturalCaseCompare);
|
||||
sort(this.media, keyfn, naturalCaseCompare);
|
||||
this.links.sort(keyfn);
|
||||
this.media.sort(keyfn);
|
||||
const elem = document.querySelector<HTMLElement>(`#${colid}`);
|
||||
const oldelem = (this.sortcol && document.querySelector<HTMLElement>(`#${this.sortcol}`));
|
||||
if (this.sortcol === colid && this.sortasc) {
|
||||
@ -280,7 +342,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
let oldmask = "";
|
||||
for (const r of this.selection) {
|
||||
const m = this.items[r].mask;
|
||||
const m = this.items.at(r).mask;
|
||||
if (oldmask && m !== oldmask) {
|
||||
oldmask = "";
|
||||
break;
|
||||
@ -292,7 +354,7 @@ class SelectionTable extends VirtualTable {
|
||||
const newmask = await ModalDialog.prompt(
|
||||
"Renaming mask", "Set new renaming mask", oldmask);
|
||||
for (const r of this.selection) {
|
||||
this.items[r].mask = newmask;
|
||||
this.items.at(r).mask = newmask;
|
||||
this.invalidateRow(r);
|
||||
}
|
||||
}
|
||||
@ -317,16 +379,6 @@ class SelectionTable extends VirtualTable {
|
||||
this.switchTab(type);
|
||||
}
|
||||
|
||||
get checkedIndexes() {
|
||||
const rv: number[] = [];
|
||||
this.items.forEach(function (item, idx) {
|
||||
if (item.matched && item.matched !== "unmanual") {
|
||||
rv.push(idx);
|
||||
}
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
|
||||
get rowCount() {
|
||||
return this.items.length;
|
||||
}
|
||||
@ -336,7 +388,7 @@ class SelectionTable extends VirtualTable {
|
||||
return false;
|
||||
}
|
||||
for (const rowid of this.selection) {
|
||||
const item = this.items[rowid];
|
||||
const item = this.items.at(rowid);
|
||||
if (!state) {
|
||||
state = matched(item) ? "unmanual" : "manual";
|
||||
}
|
||||
@ -362,7 +414,7 @@ class SelectionTable extends VirtualTable {
|
||||
selectChecked() {
|
||||
this.selection.clear();
|
||||
let min = null;
|
||||
for (const ci of this.checkedIndexes) {
|
||||
for (const ci of this.items.checked) {
|
||||
this.selection.add(ci);
|
||||
min = min === null ? ci : Math.min(min, ci);
|
||||
}
|
||||
@ -381,7 +433,7 @@ class SelectionTable extends VirtualTable {
|
||||
if (this.focusRow < 0) {
|
||||
return;
|
||||
}
|
||||
items.push(this.items[this.focusRow]);
|
||||
items.push(this.items.at(this.focusRow));
|
||||
}
|
||||
PORT.postMessage({
|
||||
msg: "openUrls",
|
||||
@ -389,14 +441,14 @@ class SelectionTable extends VirtualTable {
|
||||
});
|
||||
}
|
||||
|
||||
applyDeltaTo(delta: ItemDelta[], items: BaseMatchedItem[]) {
|
||||
applyDeltaTo(delta: ItemDelta[], items: ItemCollection) {
|
||||
const active = items === this.items;
|
||||
for (const d of delta) {
|
||||
const {idx = -1, matched = null} = d;
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
const item = items[idx];
|
||||
const item = items.byIndex(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
@ -410,7 +462,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
item.matched = matched;
|
||||
if (active) {
|
||||
this.invalidateRow(idx);
|
||||
this.invalidateRow(item.rowid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -435,7 +487,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
updateStatus() {
|
||||
const selected = this.checkedIndexes.length;
|
||||
const selected = this.items.checked.length;
|
||||
if (!selected) {
|
||||
this.status.textContent = _("noitems.label");
|
||||
}
|
||||
@ -446,7 +498,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getRowClasses(rowid: number) {
|
||||
const item = this.items[rowid];
|
||||
const item = this.items.at(rowid);
|
||||
if (!item || !matched(item) || !item.matched) {
|
||||
return null;
|
||||
}
|
||||
@ -454,7 +506,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getCellIcon(rowid: number, colid: number) {
|
||||
const item = this.items[rowid];
|
||||
const item = this.items.at(rowid);
|
||||
if (item && colid === COL_DOWNLOAD) {
|
||||
return this.icons.get(iconForPath(item.url, ICON_BASE_SIZE));
|
||||
}
|
||||
@ -471,7 +523,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getDownloadText(idx: number) {
|
||||
const item = this.items[idx];
|
||||
const item = this.items.at(idx);
|
||||
if (!item) {
|
||||
return "";
|
||||
}
|
||||
@ -482,7 +534,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getText(prop: string, idx: number) {
|
||||
const item: any = this.items[idx];
|
||||
const item: any = this.items.at(idx);
|
||||
if (!item || !(prop in item) || !item[prop]) {
|
||||
return "";
|
||||
}
|
||||
@ -490,7 +542,7 @@ class SelectionTable extends VirtualTable {
|
||||
}
|
||||
|
||||
getMaskText(idx: number) {
|
||||
const item = this.items[idx];
|
||||
const item = this.items.at(idx);
|
||||
if (item) {
|
||||
return item.mask;
|
||||
}
|
||||
@ -521,13 +573,13 @@ class SelectionTable extends VirtualTable {
|
||||
|
||||
getCellCheck(rowid: number, colid: number) {
|
||||
if (colid === COL_CHECK) {
|
||||
return !!matched(this.items[rowid]);
|
||||
return !!matched(this.items.at(rowid));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setCellCheck(rowid: number, colid: number, value: boolean) {
|
||||
this.items[rowid].matched = value ? "manual" : "unmanual";
|
||||
this.items.at(rowid).matched = value ? "manual" : "unmanual";
|
||||
this.invalidateRow(rowid);
|
||||
this.updateStatus();
|
||||
}
|
||||
@ -539,7 +591,7 @@ async function download(paused = false) {
|
||||
if (!mask) {
|
||||
throw new Error("error.invalidMask");
|
||||
}
|
||||
const items = Table.checkedIndexes;
|
||||
const items = Table.items.checkedIndexes;
|
||||
if (!items.length) {
|
||||
throw new Error("error.noItemsSelected");
|
||||
}
|
||||
@ -631,10 +683,14 @@ function setFiltersInternal(
|
||||
}
|
||||
|
||||
function setFilters(filters: any) {
|
||||
const {linkFilters = [], mediaFilters = [], activeFilters = []} = filters;
|
||||
const {
|
||||
linkFilterDescs = [],
|
||||
mediaFilterDescs = [],
|
||||
activeFilters = []
|
||||
} = filters;
|
||||
const active: Set<string> = new Set(activeFilters);
|
||||
setFiltersInternal("#linksFilters", linkFilters, active);
|
||||
setFiltersInternal("#mediaFilters", mediaFilters, active);
|
||||
setFiltersInternal("#linksFilters", linkFilterDescs, active);
|
||||
setFiltersInternal("#mediaFilters", mediaFilterDescs, active);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
|
@ -28,17 +28,17 @@
|
||||
<p class="example">../mygallery[0:9]/photo[01:10][03:1:-1].jpg</p>
|
||||
</section>
|
||||
<section id="options">
|
||||
<h2>Download</h2>
|
||||
<h2 data-i18n="download">Download</h2>
|
||||
<input type="text" id="URL">
|
||||
<h2>Custom Filename</h2>
|
||||
<h2 data-i18n="custom-filename">Custom Filename</h2>
|
||||
<input type="text" id="filename">
|
||||
<h3>Referring page</h3>
|
||||
<h3 data-i18n="referrer">Referring page</h3>
|
||||
<input type="text" id="referrer">
|
||||
<h3>Title</h3>
|
||||
<h3 data-i18n="title">Title</h3>
|
||||
<input type="text" id="title">
|
||||
<h3>Description</h3>
|
||||
<h3 data-i18n="description">Description</h3>
|
||||
<input type="text" id="description">
|
||||
<h3>Mask</h3>
|
||||
<h3 data-i18n="mask">Mask</h3>
|
||||
<div id="maskOptions">
|
||||
<input type="text" id="mask">
|
||||
<button id="maskButton" class="icon-tags"></button>
|
||||
|
Reference in New Issue
Block a user