first commit

This commit is contained in:
huangjx
2021-09-09 22:03:08 +08:00
commit f2a2365102
74 changed files with 33916 additions and 0 deletions

94
src/OC/msg.js Normal file
View File

@@ -0,0 +1,94 @@
import $ from 'jquery'
/**
* A little class to manage a status field for a "saving" process.
* It can be used to display a starting message (e.g. "Saving...") and then
* replace it with a green success message or a red error message.
*
* @namespace OC.msg
*/
export default {
/**
* Displayes a "Saving..." message in the given message placeholder
*
* @param {Object} selector Placeholder to display the message in
*/
startSaving(selector) {
this.startAction(selector, t('core', 'Saving …'))
},
/**
* Displayes a custom message in the given message placeholder
*
* @param {Object} selector Placeholder to display the message in
* @param {string} message Plain text message to display (no HTML allowed)
*/
startAction(selector, message) {
$(selector).text(message)
.removeClass('success')
.removeClass('error')
.stop(true, true)
.show()
},
/**
* Displayes an success/error message in the given selector
*
* @param {Object} selector Placeholder to display the message in
* @param {Object} response Response of the server
* @param {Object} response.data Data of the servers response
* @param {string} response.data.message Plain text message to display (no HTML allowed)
* @param {string} response.status is being used to decide whether the message
* is displayed as an error/success
*/
finishedSaving(selector, response) {
this.finishedAction(selector, response)
},
/**
* Displayes an success/error message in the given selector
*
* @param {Object} selector Placeholder to display the message in
* @param {Object} response Response of the server
* @param {Object} response.data Data of the servers response
* @param {string} response.data.message Plain text message to display (no HTML allowed)
* @param {string} response.status is being used to decide whether the message
* is displayed as an error/success
*/
finishedAction(selector, response) {
if (response.status === 'success') {
this.finishedSuccess(selector, response.data.message)
} else {
this.finishedError(selector, response.data.message)
}
},
/**
* Displayes an success message in the given selector
*
* @param {Object} selector Placeholder to display the message in
* @param {string} message Plain text success message to display (no HTML allowed)
*/
finishedSuccess(selector, message) {
$(selector).text(message)
.addClass('success')
.removeClass('error')
.stop(true, true)
.delay(3000)
.fadeOut(900)
.show()
},
/**
* Displayes an error message in the given selector
*
* @param {Object} selector Placeholder to display the message in
* @param {string} message Plain text error message to display (no HTML allowed)
*/
finishedError(selector, message) {
$(selector).text(message)
.addClass('error')
.removeClass('success')
.show()
},
}

3
src/aria2Options.js Normal file
View File

@@ -0,0 +1,3 @@
const aria2Options = ["ca-certificate", "certificate", "dht-file-path", "dht-file-path6", "dir", "input-file", "load-cookies", "log", "metalink-file", "netrc-path", "on-bt-download-complete", "on-download-complete", "on-download-error", "on-download-start", "on-download-stop", "on-download-pause", "out", "private-key", "rpc-certificate", "rpc-private-key", "save-cookies", "save-session", "server-stat-if", "server-stat-of", "torrent-file", "all-proxy", "all-proxy-passwd", "all-proxy-user", "allow-overwrite", "allow-piece-length-change", "always-resume", "async-dns", "auto-file-renaming", "bt-enable-hook-after-hash-check", "bt-enable-lpd", "bt-exclude-tracker", "bt-external-ip", "bt-force-encryption", "bt-hash-check-seed", "bt-load-saved-metadata", "bt-max-peers", "bt-metadata-only", "bt-min-crypto-level", "bt-prioritize-piece", "bt-remove-unselected-file", "bt-request-peer-speed-limit", "bt-require-crypto", "bt-save-metadata", "bt-seed-unverified", "bt-stop-timeout", "bt-tracker", "bt-tracker-connect-timeout", "bt-tracker-interval", "bt-tracker-timeout", "check-integrity", "checksum", "conditional-get", "connect-timeout", "content-disposition-default-utf8", "continue", "dir", "dry-run", "enable-http-keep-alive", "enable-http-pipelining", "enable-mmap", "enable-peer-exchange", "file-allocation", "follow-metalink", "follow-torrent", "force-save", "ftp-passwd", "ftp-pasv", "ftp-proxy", "ftp-proxy-passwd", "ftp-proxy-user", "ftp-reuse-connection", "ftp-type", "ftp-user", "gid", "hash-check-only", "header", "http-accept-gzip", "http-auth-challenge", "http-no-cache", "http-passwd", "http-proxy", "http-proxy-passwd", "http-proxy-user", "http-user", "https-proxy", "https-proxy-passwd", "https-proxy-user", "index-out", "lowest-speed-limit", "max-connection-per-server", "max-download-limit", "max-file-not-found", "max-mmap-limit", "max-resume-failure-tries", "max-tries", "max-upload-limit", "metalink-base-uri", "metalink-enable-unique-protocol", "metalink-language", "metalink-location", "metalink-os", "metalink-preferred-protocol", "metalink-version", "min-split-size", "no-file-allocation-limit", "no-netrc", "no-proxy", "out", "parameterized-uri", "pause", "pause-metadata", "piece-length", "proxy-method", "realtime-chunk-checksum", "referer", "remote-time", "remove-control-file", "retry-wait", "reuse-uri", "rpc-save-upload-metadata", "seed-ratio", "seed-time", "select-file", "split", "ssh-host-key-md", "stream-piece-selector", "timeout", "uri-selector", "use-head", "user-agent", "dry-run", "metalink-base-uri", "parameterized-uri", "pause", "piece-length", "rpc-save-upload-metadata", "bt-max-peers", "bt-request-peer-speed-limit", "bt-remove-unselected-file", "force-save", "max-download-limit", "max-upload-limit", "bt-max-open-files", "download-result", "keep-unfinished-download-result", "log", "log-level", "max-concurrent-downloads", "max-download-result", "max-overall-download-limit", "max-overall-upload-limit", "optimize-concurrent-downloads", "save-cookies", "save-session", "server-stat-of"];
export default aria2Options

306
src/autoComplete.js Normal file
View File

@@ -0,0 +1,306 @@
class autoComplete {
options;
static UP = 38;
static DOWN = 40;
static ENTER = 13;
static ESC = 27;
static entryClass = ".suggestion-item";
static entryClassContainer = ".suggestion-container";
constructor(options) {
this.options = {
selector: 0,
source: 0,
minChars: 3,
delay: 150,
offsetLeft: 0,
offsetTop: 1,
cache: 1,
menuClass: '',
renderItem: function (item, search) {
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var re = new RegExp(`(${search.split(' ').join('|')})`, "gi");
return `<div class="suggestion-item" data-val="${item}">${item.replace(re, "<b>$1</b>")}</div>`;
},
onSelect: function (e, term, item) { }
};
for (let k in this.options) {
if (options.hasOwnProperty(k)) this.options[k] = options[k];
}
if (typeof this.options.selector !== 'string' && !(this.options.selector instanceof NodeList))
throw ("invalid selecor!");
this.elements = typeof this.options.selector == 'object' ? this.options.selector : document.querySelectorAll(this.options.selector);
}
static getInstance(options) {
return new autoComplete(options);
}
attachData(element) {
element.rect = element.getBoundingClientRect();
element.sgBox = this.createSuggestionBox();
element.options = this.options;
}
run() {
for (const element of this.elements) {
this.init(element);
}
}
init(element) {
element.autocompleteAttr = element.getAttribute('autocomplete');
element.setAttribute('autocomplete', 'off');
element.cache = {};
element.lastValue = '';
this.attachData(element);
this.attach('resize', window, function (e) {
autoComplete.updateSuggestionBox(element);
});
document.body.appendChild(element.sgBox);
this.live('suggestion-item', 'mouseleave', function (e) {
var sel = element.sgBox.querySelector('.suggestion-item.selected');
if (sel)
setTimeout(function () { sel.className = sel.className.replace('selected', ''); }, 20);
}, element.sgBox);
this.live('suggestion-item', 'mouseover', function (e) {
var sel = element.sgBox.querySelector('.suggestion-item.selected');
if (sel) {
sel.classList.remove("selected");
}
this.className += ' selected';
}, element.sgBox);
const selectHandler = function (selected, element, e) {
if (autoComplete.hasClass(selected, 'suggestion-item')) {
let v = selected.getAttribute('data-val');
element.value = v;
element.options.onSelect(e, v, selected);
element.sgBox.style.display = 'none';
}
}
this.live('suggestion-item', 'mousedown,pointerdown', function (e) {
e.stopPropagation();
//this refers to the found element within;
let selected = this;
selectHandler(selected, element, e);
}, element.sgBox);
this.attach('blur', element, autoComplete.blurCallback);
this.attach('keydown', element, autoComplete.keyDownCallback);
this.attach('keyup', element, autoComplete.keyUpCallback);
if (!this.options.minChars)
this.attach('focus', element, autoComplete.focusCallback);
}
static suggest(element, data) {
if (!element) {
return;
}
let sgBox = element.sgBox, options = element.options;
var val = element.value;
element.cache[val] = data;
if (data.length && val.length >= options.minChars) {
var s = '';
for (var i = 0; i < data.length; i++) s += options.renderItem(data[i], val);
sgBox.innerHTML = s;
autoComplete.updateSuggestionBox(element, 0);
}
else {
sgBox.style.display = 'none';
}
}
static hasClass(el, className) {
return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className);
}
attach(eventType, target, selector, callback) {
if (typeof selector === 'function' && !callback) {
callback = selector;
selector = target;
}
if (typeof target === 'object') {
if (target.attachEvent) {
target.attachEvent('on' + eventType, function (e) {
callback.call(target, e);
});
}
else {
target.addEventListener(eventType, function (e) {
callback.call(target, e);
});
}
return;
}
let el = document.querySelector(target);
if (!el) {
return;
}
el.addEventListener(eventType, function (e) {
let element = e.target;
if (element === this) {
callback.call(element, e);
return;
}
for (; element && element != this; element = element.parentNode) {
if (element.matches(selector)) {
callback.call(element, e);
break;
}
}
});
}
live(elClass, event, cb, context) {
if (typeof event === 'string' && event.indexOf(',')) {
var events = event.split(',');
}
for (const event of events) {
this.attach(event, context || document, function (e) {
var found, el = e.target || e.srcElement;
while (el && !(found = autoComplete.hasClass(el, elClass)))
el = el.parentElement;
if (found) cb.call(el, e);
});
}
}
static blurCallback(e) {
let element = this
let sgBox = element.sgBox;
let hoverActive;
try {
hoverActive = document.querySelector('.suggestion-container:hover');
} catch (e) {
hoverActive = 0;
}
if (!hoverActive) {
element.lastValue = element.value;
sgBox.style.display = 'none';
setTimeout(function () { sgBox.style.display = 'none'; }, 350);
} else if (element !== document.activeElement) {
setTimeout(function () {
element.focus();
}, 20);
}
}
static keyUpCallback(e) {
let element = this;
let sgBox = element.sgBox, options = element.options;
var key = window.event ? e.keyCode : e.which;
if (!key || (key < 35 || key > 40) && ![autoComplete.ENTER, autoComplete.ESC].includes(key)) {
var val = element.value;
if (val.length >= options.minChars) {
if (val != element.lastValue) {
element.lastValue = val;
clearTimeout(element.timer);
if (options.cache) {
if (val in element.cache) {
autoComplete.suggest(element, element.cache[val]); return;
}
for (var i = 1; i < val.length - options.minChars; i++) {
var part = val.slice(0, val.length - i);
if (part in element.cache && !element.cache[part].length) {
autoComplete.suggest([]);
return;
}
}
}
const msuggest = function (data) {
autoComplete.suggest(element, data);
}
element.timer = setTimeout(function () { options.source(val, msuggest) }, options.delay);
}
} else {
element.lastValue = val;
sgBox.style.display = 'none';
}
}
};
static keyDownCallback(e) {
let element = this;
let sgBox = element.sgBox, options = element.options;
var key = window.event ? e.keyCode : e.which;
// down =40, up =38
if ((key == 40 || key == 38) && sgBox.innerHTML) {
var next, sel = sgBox.querySelector('.suggestion-item.selected');
if (!sel) {
next = (key == 40) ? sgBox.querySelector('.suggestion-item') : sgBox.childNodes[scBox.childNodes.length - 1]; // first : last
next.className += ' selected';
element.value = next.getAttribute('data-val');
} else {
next = (key == 40) ? sel.nextSibling : sel.previousSibling;
if (next) {
sel.className = sel.className.replace('selected', '');
next.className += ' selected';
element.value = next.getAttribute('data-val');
}
else {
sel.className = sel.className.replace('selected', '');
element.value = element.lastValue; next = 0;
}
}
autoComplete.updateSuggestionBox(element, 0, next);
return false;
//ESC = 27
} else if (key == 27) {
element.value = element.lastValue;
sgBox.style.display = 'none';
//enter = 13,tab = 9
} else if (key == 13 || key == 9) {
var sel = sgBox.querySelector('.suggestion-item.selected');
if (sel && sgBox.style.display != 'none') {
options.onSelect(e, sel.getAttribute('data-val'), sel);
setTimeout(function () { sgBox.style.display = 'none'; }, 20);
}
}
}
static focusCallback(e) {
let element = this;
element.lastValue = '\n';
autoComplete.keyUpCallback(e)
}
static getMaxHeight(element) {
let style = window.getComputedStyle ? getComputedStyle(element, null) : element.currentStyle;
return parseInt(style.maxHeight);
}
static updatePosition(element, rect, options) {
element.style.left = Math.round(rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + options.offsetLeft) + 'px';
element.style.top = Math.round(rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + options.offsetTop) + 'px';
element.style.width = Math.round(rect.right - rect.left) + 'px';
}
static updateSuggestionBox(element, resize, next) {
let sgBox = element.sgBox, rect = element.getBoundingClientRect(), options = element.options;
autoComplete.updatePosition(sgBox, rect, options);
if (resize && !next) {
return;
}
sgBox.style.display = 'block';
if (!sgBox.maxHeight) {
sgBox.maxHeight = autoComplete.getMaxHeight(sgBox);
}
if (!sgBox.suggestionHeight) {
sgBox.suggestionHeight = sgBox.querySelector('.suggestion-item').offsetHeight;
}
if (!sgBox.suggestionHeight) {
return;
}
if (!next) {
sgBox.scrollTop = 0;
return;
}
let scrTop = sgBox.scrollTop
let selTop = next.getBoundingClientRect().top - sgBox.getBoundingClientRect().top;
if (selTop + sgBox.suggestionHeight - sgBox.maxHeight > 0) {
sgBox.scrollTop = selTop + sgBox.suggestionHeight + scrTop - sgBox.maxHeight;
} else if (selTop < 0) {
sgBox.scrollTop = selTop + scrTop;
}
}
createSuggestionBox() {
let sgBox = document.createElement('div');
sgBox.classList.add('suggestion-container');
//suggestionBox.classList.add(options.menuClass);
return sgBox;
}
}
export default autoComplete;

39
src/buttonActions.js Normal file
View File

@@ -0,0 +1,39 @@
import $ from 'jquery'
import Http from './http'
import helper from './helper'
const buttonHandler = (event, type) => {
let element = event.target;
event.stopPropagation();
event.preventDefault();
let url = element.getAttribute("path");
let row, data = {};
let removeRow = true;
if (row = element.closest('.table-row-search')) {
data['form_input_text'] = row.dataset.link;
} else {
row = element.closest('.table-row')
data = row.dataset;
if (!data.gid) {
console.log("gid is not set!");
}
}
Http.getInstance(url).setErrorHandler(function (xhr, textStatus, error) {
console.log(error);
}).setHandler(function (data) {
if (data.hasOwnProperty('error')) {
helper.message(data['error']);
return;
}
if (data.hasOwnProperty('result')) {
helper.message("Success for " + data['result']);
}
if (row && removeRow)
row.remove();
}).setData(data).send();
}
export default {
run: function () {
$("#ncdownloader-table-wrapper").on("click", ".table-cell-action-item .button-container button", e => buttonHandler(e, ''));
}
}

42
src/eventHandler.js Normal file
View File

@@ -0,0 +1,42 @@
const eventHandler = {
add: function (eventType, target, selector, callback) {
if (typeof selector === 'function' && !callback) {
callback = selector;
selector = target;
}
if (typeof target === 'object') {
if (target.attachEvent) {
target.attachEvent('on' + eventType, function (e) {
callback.call(target, e);
});
}
else {
target.addEventListener(eventType, function (e) {
callback.call(target, e);
});
}
return;
}
let el = document.querySelector(target);
if (!el) {
return;
}
el.addEventListener(eventType, function (e) {
let element = e.target;
if (element === this && selector === target) {
callback.call(element, e);
return;
}
for (; element && element != this; element = element.parentNode) {
if (element.matches(selector)) {
callback.call(element, e);
break;
}
}
});
},
off: function (element, eventType, callback) {
element.removeEventListener(eventType, callback);
}
}
export default eventHandler;

149
src/helper.js Normal file
View File

@@ -0,0 +1,149 @@
import $ from 'jquery'
import {
generateUrl
} from '@nextcloud/router'
import Toastify from 'toastify-js'
import "toastify-js/src/toastify.css"
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import nctable from './ncTable';
import Http from './http'
const helper = {
generateUrl: generateUrl,
loop(callback, delay, ...args) {
callback(...args);
clearTimeout(helper.timeoutID);
this.polling(callback, delay, ...args);
},
enabledPolling: 1,
trim(string, char) {
return string.split(char).filter(Boolean).join(char)
},
isHtml(string) {
const htmlRegex = new RegExp('^<([a-z]+)[^>]+>(.*?)</\\1>', 'i');
return htmlRegex.test(string);
},
ucfirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
},
polling(callback, delay, ...args) {
self = this;
helper.timeoutID = setTimeout(function () {
if (self.enabledPolling) {
callback(...args);
self.polling(callback, delay, ...args);
}
}, delay);
},
isURL(url) {
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
return pattern.test(url);
},
isMagnetURI(url) {
const magnetURI = /^magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32,40}&dn=.+&tr=.+$/i;
return magnetURI.test(url.trim());
},
message: function (message) {
Toastify({
text: message,
duration: 3000,
newWindow: true,
close: true,
gravity: "top", // `top` or `bottom`
position: "center", // `left`, `center` or `right`
backgroundColor: "linear-gradient(to right, rgb(26, 28, 27), rgb(126, 138, 105))",
stopOnFocus: true, // Prevents dismissing of toast on hover
onClick: function () { } // Callback after click
}).showToast();
},
aria2Toggle: function (data) {
if (!data.status) {
return;
}
if (!data.status && data.error) {
return;
}
let $element = $("#start-aria2 button");
let aria2 = $element.attr("data-aria2");
if (aria2 === 'on') {
$element.attr("data-aria2", "off").html(t("ncdownloader", "Start Aria2"));
} else {
$element.attr("data-aria2", "on").html(t("ncdownloader", "Stop Aria2"));
}
},
getPathLast: function (path) {
return path.substring(path.lastIndexOf('/') + 1)
},
updateCounter(data) {
for (let key in data) {
const counter = document.getElementById(key + "-downloads-counter")
counter.innerHTML = '<div class="number">' + data[key] + '</div>';
}
},
refresh(path) {
path = path || "/apps/ncdownloader/status/active";
let url = helper.generateUrl(path);
Http.getInstance(url).setHandler(function (data) {
if (data && data.row) {
nctable.getInstance(data.title, data.row).create();
} else {
nctable.getInstance().noData();
}
if (data.counter)
helper.updateCounter(data.counter);
}).send();
},
html2DOM: function (htmlString) {
const parser = new window.DOMParser();
let doc = parser.parseFromString(htmlString, "text/html")
return doc.querySelector("div");
},
makePair: function (data, prefix = "aria2-settings") {
for (let key in data) {
let index;
if ((index = key.indexOf(prefix + "-key-")) !== -1) {
let valueKey = prefix + "-value-" + key.substring(key.lastIndexOf('-') + 1);
if (!data[valueKey]) continue;
let newkey = data[key];
data[newkey] = data[valueKey];
delete data[key];
delete data[valueKey];
}
}
},
getData(selector) {
const element = typeof selector === "object" ? selector : document.getElementById(selector)
const data = {}
data['path'] = element.getAttribute('path') || '';
//if the targeted element is not of input or select type, search for such elements below it
if (!['SELECT', 'INPUT'].includes(element.nodeName.toUpperCase())) {
const nodeList = element.querySelectorAll('input,select')
for (let i = 0; i < nodeList.length; i++) {
const element = nodeList[i]
if (element.hasAttribute('type') && element.getAttribute('type') === 'button') {
continue
}
const key = element.getAttribute('id')
data[key] = element.value
for (let prop in element.dataset) {
data[prop] = element.dataset[prop];
}
}
} else {
for (let prop in element.dataset) {
data[prop] = element.dataset[prop];
}
const key = element.getAttribute('id')
data[key] = element.value
}
return data;
}
}
export default helper

52
src/http.js Normal file
View File

@@ -0,0 +1,52 @@
const Http = class {
data;
constructor(url) {
this.url = url;
this.method = 'POST';
this.data = null;
this.dataType = 'application/json';
this.xhr = new XMLHttpRequest();
}
static getInstance(url) {
return new Http(url);
}
setData(data) {
this.data = data
return this
}
send() {
let token = this.getToken();
this.xhr.open(this.method, this.url);
this.xhr.setRequestHeader('requesttoken', token)
this.xhr.setRequestHeader('OCS-APIREQUEST', 'true')
this.xhr.setRequestHeader('Content-Type', this.dataType);
let callback = this.handler;
this.xhr.onload = () => {
if (typeof callback === 'function')
callback(JSON.parse(this.xhr.response));
}
this.xhr.onerror = this.errorHandler;
this.xhr.send(JSON.stringify(this.data));
}
getToken() {
return document.getElementsByTagName('head')[0].getAttribute('data-requesttoken')
}
setUrl(url) {
this.url = url
return this
}
setMethod(method) {
this.method = method
return this
}
setHandler(handler) {
this.handler = handler || function (data) { };
return this;
}
setErrorHandler(handler) {
this.errorHandler = handler
return this;
}
}
export default Http

48
src/index.js Normal file
View File

@@ -0,0 +1,48 @@
import helper from './helper'
import $ from 'jquery'
import Http from './http'
//import actionLinks from './actionLinks'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import inputAction from './inputAction'
import updatePage from './updatePage'
import buttonActions from './buttonActions'
import inputBox from './inputBox'
'use strict'
const basePath = "/apps/ncdownloader";
$(document).on('ajaxSend', function (elm, xhr, settings) {
let token = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken')
if (settings.crossDomain === false) {
xhr.setRequestHeader('requesttoken', token)
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
}
})
window.addEventListener('DOMContentLoaded', function () {
document.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
event.preventDefault();
return false;
}
}
);
inputAction.run();
updatePage.run();
buttonActions.run();
$("#ncdownloader-form-wrapper").append(inputBox.getInstance(t("ncdownloader", 'New Download')).create());
$("#start-aria2").on("click", function (e) {
const path = basePath + "/aria2/start";
let url = helper.generateUrl(path);
Http.getInstance(url).setHandler(function (data) {
helper.aria2Toggle(data);
}).send();
})
$('#ncdownloader-user-settings button').on("click", function (e) {
let link = helper.generateUrl(e.target.getAttribute('path'));
window.location.href = link;
})
});

104
src/inputAction.js Normal file
View File

@@ -0,0 +1,104 @@
import helper from './helper'
import $ from 'jquery'
import Http from './http'
import 'tippy.js/dist/tippy.css';
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import inputBox from './inputBox'
import nctable from './ncTable'
const basePath = "/apps/ncdownloader";
const createInputBox = (event, type) => {
event.preventDefault();
event.stopPropagation();
//let id = event.target.closest("div").getAttribute('id');
let inputID = event.target.closest("div").dataset.inputbox;
let inputElement = inputID ? document.getElementById(inputID) : null;
if (inputElement) {
inputElement.remove();
}
let height = $(window).scrollTop();
if (height > 50)
$("html, body").animate({ scrollTop: 0 }, "fast");
let name;
switch (type) {
case "ytdl":
name = t("ncdownloader", 'YTDL Download');
break;
case "search":
name = t("ncdownloader", 'Search');
break;
default:
name = t("ncdownloader", 'New Download');
}
let container;
if (type === 'search') {
container = inputBox.getInstance(name, type).addSpinner().create();
//container.appendChild(inputBox.createLoading());
} else {
container = inputBox.getInstance(name, type).create();
}
$("#ncdownloader-form-wrapper").append(container);
}
const toggleButton = element => {
if (!element.previousSibling) {
return;
}
if (element.style.display === 'none') {
element.style.display = 'block'
element.previousSibling.style.display = 'none';
} else {
element.style.display = 'none'
element.previousSibling.style.display = 'block';
}
}
const inputHandler = (event) => {
event.preventDefault();
let element = event.target;
// element.textContent = '';
//$(element).append(inputBox.createLoading());
toggleButton(element);
let inputData = helper.getData('form-input-wrapper');
let inputValue = inputData.form_input_text;
if (inputData.type !== 'search' && !helper.isURL(inputValue) && !helper.isMagnetURI(inputValue)) {
helper.message(t("ncdownloader", "Invalid url"));
return;
}
if (inputData.type === 'ytdl') {
helper.message(t("ncdownloader", "YTDL Download initiated"));
}
if (inputData.type === 'search') {
nctable.getInstance().loading();
}
const successCallback = (data, element) => {
//data = JSON.parse(data.target.response)
if (data !== null && data.hasOwnProperty("file")) {
helper.message(t("ncdownloader", "Downloading" + " " + data.file));
}
toggleButton(element);
if (data && data.title) {
helper.enabledPolling = 0;
const tableInst = nctable.getInstance(data.title, data.row);
tableInst.actionLink = false;
tableInst.rowClass = "table-row-search";
tableInst.create();
}
}
const path = inputData.path || basePath + "/new";
let url = helper.generateUrl(path);
Http.getInstance(url).setData(inputData).setHandler(function (data) {
successCallback(data, element);
}).send();
}
export default {
run: function () {
$("#app-navigation").on("click", "#new-download-ytdl", (event) => createInputBox(event, 'ytdl'));
$("#app-navigation").on("click", "#new-download", (event) => createInputBox(event, ''));
$("#app-navigation").on("click", "#torrent-search-button", (event) => createInputBox(event, 'search'));
$("#ncdownloader-form-wrapper").on("click", "#form-input-button", (event) => inputHandler(event))
}
}

62
src/inputBox.js Normal file
View File

@@ -0,0 +1,62 @@
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import helper from './helper'
class inputBox {
constructor(name, id) {
this.name = name;
this.container = this._createForm();
this.textInput = this._createTextInput(id);
this.controlsContainer = this._createControlsContainer();
}
static getInstance(name, id) {
return new inputBox(name, id);
}
create() {
this.container.appendChild(this.textInput);
this.controlsContainer.appendChild(this._createControls());
this.container.appendChild(this.controlsContainer);
return this.container;
}
_createControlsContainer() {
let div = document.createElement("div");
div.classList.add("controls-container");
return div;
}
_createForm() {
let container = document.createElement("form");
container.classList.add("form-input-wrapper");
container.setAttribute('id', 'form-input-wrapper');
return container;
}
_createTextInput(id) {
id = id || 'general';
let textInput = document.createElement('input');
textInput.setAttribute('type', 'text');
textInput.setAttribute('id', "form_input_text");
textInput.setAttribute('data-type', id);
textInput.setAttribute('value', '');
textInput.classList.add('form-input-text');
return textInput;
}
_createControls() {
let button = document.createElement('button');
button.setAttribute('type', this.name);
button.setAttribute('id', 'form-input-button');
//buttonInput.setAttribute('value', t('ncdownloader', helper.ucfirst(name)));
let text = document.createTextNode(t('ncdownloader', helper.ucfirst(this.name)));
button.appendChild(text);
return button;
}
addSpinner() {
const parser = new window.DOMParser();
let htmlString = '<button class="bs-spinner"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" disabled></span><span class="visually-hidden">Loading...</span></button>'
let doc = parser.parseFromString(htmlString, "text/html")
let element = doc.querySelector(".bs-spinner");
element.style.display = 'none';
this.controlsContainer.appendChild(element);
return this;
}
}
export default inputBox;

145
src/ncTable.js Normal file
View File

@@ -0,0 +1,145 @@
import helper from './helper'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
class ncTable {
actionLink = true;
bodyClass = "ncdownloader-table-data";
rowClass = "table-row";
headingClass = "table-heading";
cellClass = "table-cell";
//this is the parent element the table is going to append to
tableContainer = 'ncdownloader-table-wrapper';
numRow;
table;
constructor(heading, rows) {
this.table = document.getElementById(this.tableContainer);
if (heading && rows) {
this.table.innerHTML = '';
this.rows = rows;
this.heading = heading;
this.actionButtons = [];
}
}
static getInstance(heading, row) {
return new ncTable(heading, row);
}
create() {
let thead = this.createHeading()
let tbody = this.createRow();
this.table.appendChild(thead);
this.table.appendChild(tbody);
return this;
}
clear() {
this.table.innerHTML = '';
}
loading() {
let htmlStr = '<div class="text-center"><div class="spinner-border" role="status"> <span class="visually-hidden">Loading...</span></div></div>'
this.table.innerHTML = htmlStr;
return this;
}
noData() {
this.clear();
let div = document.createElement('div');
div.classList.add("no-items");
div.appendChild(document.createTextNode(t("ncdownloader", 'No items')));
this.table.appendChild(div);
}
createHeading(prefix = "table-heading") {
let thead = document.createElement("section");
thead.classList.add(this.headingClass);
let headRow = document.createElement("header");
headRow.classList.add(this.rowClass);
thead.classList.add(this.headingClass);
this.heading.forEach(name => {
let rowItem = document.createElement("div");
rowItem.classList.add(prefix + "-" + name.toLowerCase());
rowItem.classList.add(this.cellClass);
let text = document.createTextNode(t("ncdownloader", helper.ucfirst(name)));
rowItem.appendChild(text);
headRow.appendChild(rowItem);
})
thead.appendChild(headRow);
return thead;
}
createRow() {
let tbody = document.createElement("section");
tbody.classList.add(this.bodyClass);
tbody.classList.add("table-body");
let row;
for (const element of this.rows) {
if (element === null) {
continue;
}
row = document.createElement("div");
row.classList.add(this.rowClass);
let text;
for (let key in element) {
if (key.substring(0, 4) == 'data') {
let name = key.replace("_", "-");
row.setAttribute(name, element[key]);
row.setAttribute("id", element[key]);
continue;
}
let rowItem = document.createElement("div");
rowItem.classList.add(this.cellClass);
if (key === 'actions') {
rowItem.classList.add([this.cellClass, "action-item"].join("-"));
let container = document.createElement("div");
container.classList.add("button-container");
element[key].forEach(value => {
container.appendChild(this.createActionButton(value.name, value.path));
})
rowItem.appendChild(container);
row.appendChild(rowItem);
continue;
}
if (typeof element[key] === 'object') {
let child = element[key];
let div;
child.forEach(ele => {
div = document.createElement('div');
if (helper.isHtml(ele)) {
div.innerHTML = ele;
} else {
text = document.createTextNode(ele);
div.appendChild(text);
}
rowItem.appendChild(div);
})
rowItem.setAttribute("id", [this.cellClass, key].join("-"));
row.appendChild(rowItem);
continue;
}
text = document.createTextNode(element[key]);
rowItem.appendChild(text);
rowItem.setAttribute("id", [this.cellClass, key].join("-"));
row.appendChild(rowItem);
}
tbody.appendChild(row);
}
return tbody;
}
createActionButton(name, path) {
let button = document.createElement("button");
button.classList.add("icon-" + name);
button.setAttribute("path", path);
return button;
}
createActionCell(cell) {
let div = document.createElement("div");
let button = document.createElement("button");
button.classList.add("icon-more", "action-button");
button.setAttribute("id", "action-links-button");
div.classList.add("action-item");
div.appendChild(button);
//div.appendChild(actionLinks);
cell.appendChild(div);
}
}
export default ncTable;

105
src/settings.js Normal file
View File

@@ -0,0 +1,105 @@
import Http from './http'
import OC_msg from './OC/msg'
import {
generateUrl
} from '@nextcloud/router'
import settingsForm from './settingsForm'
import autoComplete from './autoComplete';
import eventHandler from './eventHandler';
import aria2Options from './aria2Options';
import helper from './helper';
'use strict';
window.addEventListener('DOMContentLoaded', function () {
eventHandler.add('click', '.ncdownloader-admin-settings', 'input[type="button"]', function (event) {
e.stopPropagation();
OC_msg.startSaving('#ncdownloader-message-banner');
const target = this.getAttribute("data-rel");
const path = inputData.url || "/apps/ncdownloader/admin/save";
let url = generateUrl(path);
Http.getInstance(url).setData(helper.getData(target)).setHandler(function () {
OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK");
}).send();
});
eventHandler.add('click', '.ncdownloader-personal-settings', 'input[type="button"]', function (event) {
event.preventDefault();
event.stopPropagation();
if (event.target.matches('.custom-aria2-settings-container')) {
return;
}
OC_msg.startSaving('#ncdownloader-message-banner');
const target = this.getAttribute("data-rel");
let inputData = helper.getData(target);
const path = inputData.url || "/apps/ncdownloader/personal/save";
let url = generateUrl(path);
Http.getInstance(url).setData(inputData).setHandler(function (data) {
OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK");
}).send();
});
eventHandler.add('click', '#custom-aria2-settings-container', "button.add-custom-aria2-settings", function (e) {
e.preventDefault();
e.stopPropagation();
let element = e.target;
let selector = "#aria2-settings-key-1";
let form = settingsForm.getInstance();
let nodeList, key, value;
nodeList = document.querySelectorAll("[id^='aria2-settings-key']")
if (nodeList.length === 0) {
key = "aria2-settings-key-1";
value = "aria2-settings-value-1";
} else {
let index = nodeList.length + 1;
key = "aria2-settings-key-" + index;
value = "aria2-settings-value-" + index;
selector = "[id^='aria2-settings-key']";
}
element.before(form.createCustomInput(key, value));
//appended the latest one
nodeList = document.querySelectorAll("[id^='aria2-settings-key']")
try {
autoComplete.getInstance({
selector: (nodeList.length !== 0) ? nodeList : selector,
minChars: 1,
source: function (term, suggest) {
term = term.toLowerCase();
let suggestions = [], data = aria2Options;
for (const item of data) {
if (item.toLowerCase().indexOf(term, 0) !== -1) {
suggestions.push(item);
}
}
suggest(suggestions);
}
}).run();
} catch (error) {
console.error(error);
}
}
)
eventHandler.add("click", "#custom-aria2-settings-container", "button.save-custom-aria2-settings", function (e) {
e.stopImmediatePropagation();
let data = helper.getData(this.getAttribute("data-rel"));
let url = generateUrl(data.path);
delete data.path;
OC_msg.startSaving('.message-banner');
helper.makePair(data);
Http.getInstance(url).setData(data).setHandler(function (data) {
OC_msg.finishedSuccess('.message-banner', "OK");
}).send();
})
eventHandler.add('click', '.ncdownloader-personal-settings', 'button.icon-close', function (e) {
e.stopImmediatePropagation();
e.preventDefault();
this.parentNode.remove();
})
Http.getInstance(generateUrl("/apps/ncdownloader/personal/aria2/get")).setHandler(function (data) {
if (!data) {
return;
}
let input = [];
for (let key in data) {
input.push({ name: key, value: data[key], id: key });
}
settingsForm.getInstance().render(input);
}).send();
});

110
src/settingsForm.js Normal file
View File

@@ -0,0 +1,110 @@
class settingsForm {
parent = "custom-aria2-settings-container";
constructor() {
}
static getInstance() {
return new this();
}
create(parent, element) {
let label = this._createLabel(element.name, element.id)
let input = this._createInput(element);
//let saveBtn = this._createSaveBtn(element.id);
let cancelBtn = this._createCancelBtn("has-content");
let container = this._createContainer(element.id);
[label, input, cancelBtn].forEach(ele => {
container.appendChild(ele);
})
let button;
if (button = parent.querySelector('button.add-custom-aria2-settings')) {
return parent.insertBefore(container, button);
}
return parent.appendChild(container);
}
createCustomInput(keyId, valueId) {
let div = this._createContainer(keyId + "-container")
div.appendChild(this._createInput({ id: keyId }));
div.appendChild(this._createInput({ id: valueId }));
div.appendChild(this._createCancelBtn());
return div;
}
createInput(element) {
let div = document.createElement("div");
div.classList.add(this.parent);
/* element.forEach(element => {
let label = document.createElement('label');
label.setAttribute("for", element.id);
let text = document.createTextNode(element.name);
label.appendChild(text);
div.appendChild(label);
// div.appendChild(this._createInput(element));
});*/
div.appendChild(this._createInput(element));
let button = document.createElement("button");
//button.setAttribute("type",'button')
button.classList.add("icon-close");
div.appendChild(button);
button = document.createElement("input");
button.setAttribute('type', 'button');
button.setAttribute('value', 'save');
button.setAttribute("data-rel", this.parent);
div.appendChild(button);
return div;
}
_createContainer(id) {
let div = document.createElement("div");
div.classList.add(id);
return div;
}
_createCancelBtn(className = '') {
let button = document.createElement("button");
if (className)
button.classList.add(className);
//button.setAttribute("type",'button')
button.classList.add("icon-close");
return button;
}
_createSaveBtn(id) {
let button = document.createElement("input");
button.setAttribute('type', 'button');
button.setAttribute('value', 'save');
button.setAttribute("data-rel", id + "-container");
return button;
}
_createLabel(name, id) {
name = name.replace('_', '-');
let label = document.createElement("lable");
label.setAttribute("for", id);
let text = document.createTextNode(name);
label.appendChild(text);
return label;
}
_createInput(data) {
let input = document.createElement('input');
let type = data.type || "text";
let placeholder = data.placeholder || '';
let value = data.value || placeholder;
input.setAttribute('type', type);
input.setAttribute('id', data.id);
input.setAttribute("name", data.name || data.id);
if (type === 'text') {
input.setAttribute('value', value);
input.setAttribute('placeholder', value);
}
input.classList.add('form-input-' + type);
return input;
}
render(data) {
let parent = document.getElementById(this.parent)
for (const element of data) {
this.create(parent, element)
}
}
}
export default settingsForm

42
src/updatePage.js Normal file
View File

@@ -0,0 +1,42 @@
import helper from './helper'
import $ from 'jquery'
import Http from './http'
const basePath = "/apps/ncdownloader/status/";
const tableContainer = ".table";
export default {
run: function () {
const eventHandler = (event, type) => {
event.preventDefault();
const path = basePath + type;
let name = type + "-downloads";
//avoid repeated click
if ($(tableContainer).attr("type") === name && helper.enabledPolling) {
return;
}
helper.enabledPolling = 1;
$(tableContainer).removeClass().addClass("table " + name);
$(tableContainer).attr("type", name);
let delay = 15000;
if (name === "active-downloads") {
delay = 1500;
}
helper.loop(helper.refresh, delay, ...[path])
};
$(".waiting-downloads").on("click", event => eventHandler(event, 'waiting'));
$(".complete-downloads").on("click", event => eventHandler(event, 'complete'));
$(".active-downloads").on("click", event => eventHandler(event, 'active'));
$(".fail-downloads").on("click", event => eventHandler(event, 'fail'));
helper.refresh(basePath + "waiting")
helper.refresh(basePath + "complete")
helper.refresh(basePath + "fail")
helper.loop(helper.refresh, 1000, basePath + "active");
helper.polling(function (url) {
url = helper.generateUrl(url);
Http.getInstance(url).setMethod('GET').send();
}, 60000, "/apps/ncdownloader/update");
}
}