46 Commits

Author SHA1 Message Date
2ccc12de90 Version 4.0.6 2019-09-02 11:53:03 +02:00
28e6866db8 Use currentWindow instead of WINDOW_ID_CURRENT 2019-09-02 04:06:28 +02:00
7185964649 emulate must use correct window
Closes #41
2019-09-02 04:02:50 +02:00
cd1005823d Typo fixes 2019-09-02 03:57:21 +02:00
0afceb9850 Dutch translation
Closes #39
2019-09-02 00:30:45 +02:00
e6dc205b9d Small de improvement 2019-09-02 00:26:35 +02:00
8e473c778b Rename to zh-CN
Related to #38
2019-09-02 00:26:15 +02:00
7a71ae5f37 Also split ui locales by _ 2019-09-02 00:24:11 +02:00
d04f3db22f Add Simplified Chinese translation
Created this translation by using the online translation tool from https://downthemall.github.io/translate/ .
2019-09-02 00:23:51 +02:00
adc6b9dbb2 Add et locale 2019-09-02 00:03:29 +02:00
e8f09c80f3 Do not add empty separator in URLMenuFilter 2019-09-01 15:24:14 +02:00
46c4e66558 webpack config updates 2019-09-01 06:17:05 +02:00
a8ea416a67 Remove smartphone section from issue template 2019-09-01 05:06:30 +02:00
320c1ddafa Add a little margin to allow focus outlines 2019-09-01 05:01:49 +02:00
4c576ba720 Add content_security_policy 2019-09-01 04:52:57 +02:00
7b58779f9e Remove outdated todos 2019-09-01 00:37:10 +02:00
45d835fe19 use correct autoHide HTML spelling 2019-09-01 00:03:53 +02:00
2d2826d192 Correctly toggle URLMenuFilter 2019-09-01 00:03:34 +02:00
4c77ad0f1f Fix popup height flash bug 2019-08-31 23:16:46 +02:00
4d72ac4534 Notifications galore 2019-08-31 23:08:12 +02:00
cdda0835d8 Wrong translation 2019-08-31 18:12:37 +02:00
78e91304eb Version 4.0.5 2019-08-31 15:44:35 +02:00
0702631003 nag a little later 2019-08-31 13:40:54 +02:00
c45bf671fb fixes to polish translation (#34) 2019-08-31 13:38:47 +02:00
33a3e275fc Create messages.json 2019-08-31 08:16:29 +02:00
369514f155 Add lt locale 2019-08-30 17:02:08 +02:00
494479ce1a Version 4.0.4 2019-08-29 23:09:14 +02:00
98ebb160f9 Silence a console.log 2019-08-29 19:37:09 +02:00
d1cc406f05 select: frontend/backend mappings were out of sync
Closes #27
2019-08-29 19:36:06 +02:00
687b6e1aa9 pl locale
Closes #19
2019-08-29 09:52:26 +02:00
c960d48b72 es-ES locale
Closes #26
2019-08-29 08:24:11 +02:00
c8c7506efc Properly translate Filters heading
Reported in #26
2019-08-29 08:14:22 +02:00
91edcee28c FIx filters message names 2019-08-29 08:14:22 +02:00
a425a786ef Do not log "custom" 2019-08-29 08:14:22 +02:00
f023351acc Update Readme.md 2019-08-28 18:39:44 +02:00
c8610eee29 Add some additional file exts to the def filters 2019-08-28 17:59:29 +02:00
f7a70ec2ea Add fr-FR locale 2019-08-28 17:16:34 +02:00
69d8ffe8a5 Minor typos 2019-08-28 05:52:32 +02:00
71240ec1e8 More typos 2019-08-28 05:45:06 +02:00
a6f3c7a647 Some typos 2019-08-28 05:41:41 +02:00
eab9631a11 Fix typo 2019-08-28 04:10:24 +02:00
9e29db911c Update dependencies 2019-08-27 19:37:55 +02:00
8d1040115a Update information about translations 2019-08-27 19:32:11 +02:00
3551545eae add DE locale 2019-08-27 19:12:36 +02:00
01001dc7b2 Add missing i18n and fix some typos 2019-08-27 18:53:31 +02:00
b4e6ab80d2 Add locale descriptions 2019-08-27 16:43:49 +02:00
41 changed files with 11566 additions and 649 deletions

View File

@ -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.

View File

@ -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,19 +55,23 @@ 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)

View File

@ -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).

View File

@ -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.

View File

@ -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

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

File diff suppressed because it is too large Load Diff

1170
_locales/et/messages.json Normal file

File diff suppressed because it is too large Load Diff

1178
_locales/fr/messages.json Normal file

File diff suppressed because it is too large Load Diff

1170
_locales/lt/messages.json Normal file

File diff suppressed because it is too large Load Diff

1170
_locales/nl/messages.json Normal file

File diff suppressed because it is too large Load Diff

1170
_locales/pl/messages.json Executable file

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -80,7 +80,11 @@
"tif",
"tiff",
"wmf",
"webp"
"webp",
"heic",
"heif",
"jxr",
"wdp"
],
"video": [
"3g2",

View File

@ -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,

View File

@ -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);
}

View File

@ -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;
}

View File

@ -123,9 +123,13 @@ async function loadRawLocales() {
const langs = new Set<string>(["en"]);
const ui = (browser.i18n || chrome.i18n).getUILanguage();
langs.add(ui);
if (ui.includes("-")) {
// Try the base too
langs.add(ui.split(/[_-]+/)[0]);
if (ui.includes("-")) {
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));

View File

@ -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) {

View File

@ -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

View File

@ -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",

View File

@ -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": {

View File

@ -49,7 +49,7 @@ p.example {
}
#options > * {
margin: 0;
margin: 2px;
}
#options > input {

View File

@ -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"],

View File

@ -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
View 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));
});

View File

@ -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 {

View File

@ -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>

View File

@ -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 = () => {

View File

@ -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));
});

View File

@ -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";

View File

@ -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 {

View File

@ -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>

View File

@ -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

View File

@ -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">

View File

@ -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() {

View File

@ -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>

819
yarn.lock

File diff suppressed because it is too large Load Diff