mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-26 19:26:59 +03:00
WebUI: Add RSS functionality
Implemented RSS Reader and AutoDownloader in reference WebUI.
This commit is contained in:
parent
a1faef0a3c
commit
2b3c92a4a8
18 changed files with 2902 additions and 27 deletions
55
src/webui/www/private/confirmfeeddeletion.html
Normal file
55
src/webui/www/private/confirmfeeddeletion.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Deletion confirmation)QBT_TR[CONTEXT=RSSWidget]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const paths = decodeURIComponent(new URI().getData('paths')).split('|');
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.closeWindows();
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
paths.forEach((path) => {
|
||||
new Request({
|
||||
url: '/api/v2/rss/removeItem',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
path: path
|
||||
},
|
||||
onComplete: (response) => {
|
||||
++completionCount;
|
||||
if (completionCount === paths.length) {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p>QBT_TR(Are you sure you want to delete the selected RSS feeds?)QBT_TR[CONTEXT=RSSWidget]</p>
|
||||
<div style="text-align: right;">
|
||||
<input type="button" id="cancelBtn" value="QBT_TR(No)QBT_TR[CONTEXT=MainWindow]" />
|
||||
<input type="button" id="confirmBtn" value="QBT_TR(Yes)QBT_TR[CONTEXT=MainWindow]" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
48
src/webui/www/private/confirmruleclear.html
Normal file
48
src/webui/www/private/confirmruleclear.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Clear downloaded episodes)QBT_TR[CONTEXT=AutomatedRssDownloader]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const rules = decodeURIComponent(new URI().getData('rules')).split('|');
|
||||
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('clearRulesPage'));
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
rules.forEach((rule) => {
|
||||
window.parent.qBittorrent.RssDownloader.modifyRuleState(rule, 'previouslyMatchedEpisodes', [], () => {
|
||||
++completionCount;
|
||||
if (completionCount === rules.length) {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('clearRulesPage'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p>QBT_TR(Are you sure you want to clear the list of downloaded episodes for the selected rule?)QBT_TR[CONTEXT=AutomatedRssDownloader]</p>
|
||||
<div style="text-align: right;">
|
||||
<input type="button" id="cancelBtn" value="QBT_TR(No)QBT_TR[CONTEXT=MainWindow]" />
|
||||
<input type="button" id="confirmBtn" value="QBT_TR(Yes)QBT_TR[CONTEXT=MainWindow]" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
56
src/webui/www/private/confirmruledeletion.html
Normal file
56
src/webui/www/private/confirmruledeletion.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Rule deletion confirmation)QBT_TR[CONTEXT=AutomatedRssDownloader]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const rules = decodeURIComponent(new URI().getData('rules')).split('|');
|
||||
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('removeRulePage'));
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
rules.forEach((rule) => {
|
||||
new Request({
|
||||
url: '/api/v2/rss/removeRule',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: rule
|
||||
},
|
||||
onComplete: (response) => {
|
||||
++completionCount;
|
||||
if (completionCount === rules.length) {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('removeRulePage'));
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p>QBT_TR(Are you sure you want to remove the selected download rules?)QBT_TR[CONTEXT=AutomatedRssDownloader]</p>
|
||||
<div style="text-align: right;">
|
||||
<input type="button" id="cancelBtn" value="QBT_TR(No)QBT_TR[CONTEXT=MainWindow]" />
|
||||
<input type="button" id="confirmBtn" value="QBT_TR(Yes)QBT_TR[CONTEXT=MainWindow]" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -34,7 +34,6 @@ Required by:
|
|||
}
|
||||
|
||||
.tab-menu li {
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0 0 5px 0;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
<li><a id="showStatusBarLink"><img class="MyMenuIcon" src="icons/checked.svg" alt="QBT_TR(Status Bar)QBT_TR[CONTEXT=MainWindow]" width="16" height="16"/>QBT_TR(Status Bar)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="speedInBrowserTitleBarLink"><img class="MyMenuIcon" src="icons/checked.svg" alt="QBT_TR(Speed in Title Bar)QBT_TR[CONTEXT=MainWindow]" width="16" height="16"/>QBT_TR(Speed in Title Bar)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="showSearchEngineLink"><img class="MyMenuIcon" src="icons/checked.svg" alt="QBT_TR(Search Engine)QBT_TR[CONTEXT=MainWindow]" width="16" height="16"/>QBT_TR(Search Engine)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li><a id="showRssReaderLink"><img class="MyMenuIcon" src="icons/checked.svg" alt="QBT_TR(RSS)QBT_TR[CONTEXT=MainWindow]" width="16" height="16"/>QBT_TR(RSS Reader)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li class="divider"><a id="StatisticsLink"><img class="MyMenuIcon" src="icons/view-statistics.svg" alt="QBT_TR(Statistics)QBT_TR[CONTEXT=MainWindow]" width="16" height="16"/>QBT_TR(Statistics)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -112,6 +113,7 @@
|
|||
<ul id="mainWindowTabsList" class="tab-menu">
|
||||
<li id="transfersTabLink" class="selected"><a class="tab">QBT_TR(Transfers)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="searchTabLink"><a class="tab">QBT_TR(Search)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
<li id="rssTabLink"><a class="tab">QBT_TR(RSS)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
|
78
src/webui/www/private/newfeed.html
Normal file
78
src/webui/www/private/newfeed.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Please type a RSS feed URL)QBT_TR[CONTEXT=RSSWidget]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('feedURL').focus();
|
||||
const path = decodeURIComponent(new URI().getData('path'));
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const feedURL = $('feedURL').value.trim();
|
||||
if (feedURL === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
$('submitButton').disabled = true;
|
||||
|
||||
new Request({
|
||||
url: '/api/v2/rss/addFeed',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
url: feedURL,
|
||||
path: path ? (path + '\\' + feedURL) : ''
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
window.parent.closeWindows();
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
$('submitButton').disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p style="font-weight: bold;">QBT_TR(Feed URL:)QBT_TR[CONTEXT=RSSWidget]</p>
|
||||
<input type="text" id="feedURL" value="" maxlength="100" style="width: 320px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(OK)QBT_TR[CONTEXT=HttpServer]" id="submitButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
77
src/webui/www/private/newfolder.html
Normal file
77
src/webui/www/private/newfolder.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Please choose a folder name)QBT_TR[CONTEXT=RSSWidget]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('folderName').focus();
|
||||
const path = decodeURIComponent(new URI().getData('path'));
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const folderName = $('folderName').value.trim();
|
||||
if (folderName === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
$('submitButton').disabled = true;
|
||||
|
||||
new Request({
|
||||
url: '/api/v2/rss/addFolder',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
path: path ? (path + '\\' + folderName) : folderName
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
window.parent.closeWindows();
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
$('submitButton').disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p style="font-weight: bold;">QBT_TR(Folder name:)QBT_TR[CONTEXT=RSSWidget]</p>
|
||||
<input type="text" id="folderName" value="" maxlength="100" style="width: 320px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(OK)QBT_TR[CONTEXT=HttpServer]" id="submitButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
70
src/webui/www/private/newrule.html
Normal file
70
src/webui/www/private/newrule.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(New rule name)QBT_TR[CONTEXT=AutomatedRssDownloader]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('name').focus();
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const name = $('name').value.trim();
|
||||
if (name === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
$('submitButton').disabled = true;
|
||||
new Request({
|
||||
url: '/api/v2/rss/setRule',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: name,
|
||||
ruleDef: '{}'
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p>QBT_TR(Please type the name of the new download rule.)QBT_TR[CONTEXT=AutomatedRssDownloader]</p>
|
||||
<input type="text" id="name" value="" maxlength="100" style="width: 320px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(OK)QBT_TR[CONTEXT=HttpServer]" id="submitButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
88
src/webui/www/private/rename_feed.html
Normal file
88
src/webui/www/private/rename_feed.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Please choose a new name for this RSS feed)QBT_TR[CONTEXT=RSSWidget]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('renameButton').click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
const oldPath = decodeURIComponent(new URI().getData('oldPath'));
|
||||
|
||||
$('rename').value = oldPath;
|
||||
$('rename').focus();
|
||||
$('rename').setSelectionRange(0, oldPath.length);
|
||||
|
||||
$('renameButton').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const newPath = $('rename').value.trim();
|
||||
if (newPath === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPath === oldPath) {
|
||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
$('renameButton').disabled = true;
|
||||
|
||||
new Request({
|
||||
url: '/api/v2/rss/moveItem',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
itemPath: oldPath,
|
||||
destPath: newPath
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
window.parent.closeWindows();
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409) {
|
||||
alert(response.responseText);
|
||||
}
|
||||
$('renameButton').disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p style="font-weight: bold;">QBT_TR(New feed name:)QBT_TR[CONTEXT=RSSWidget]</p>
|
||||
<input type="text" id="rename" value="" maxlength="100" style="width: 320px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" id="renameButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
81
src/webui/www/private/rename_rule.html
Normal file
81
src/webui/www/private/rename_rule.html
Normal file
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${LANG}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Rule renaming)QBT_TR[CONTEXT=AutomatedRssDownloader]</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=${CACHEID}" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('renameButton').click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
const oldName = decodeURIComponent(new URI().getData('rule'));
|
||||
|
||||
$('rename').value = oldName;
|
||||
$('rename').focus();
|
||||
$('rename').setSelectionRange(0, oldName.length);
|
||||
|
||||
$('renameButton').addEvent('click', (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const newName = $('rename').value.trim();
|
||||
if (newName === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName === oldName) {
|
||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
||||
return;
|
||||
}
|
||||
|
||||
$('renameButton').disabled = true;
|
||||
new Request({
|
||||
url: '/api/v2/rss/renameRule',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: oldName,
|
||||
newRuleName: newName
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p>QBT_TR(Please type the new rule name)QBT_TR[CONTEXT=AutomatedRssDownloader]</p>
|
||||
<input type="text" id="rename" value="" maxlength="100" style="width: 320px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" id="renameButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -34,6 +34,7 @@ let queueing_enabled = true;
|
|||
let serverSyncMainDataInterval = 1500;
|
||||
let customSyncMainDataInterval = null;
|
||||
let searchTabInitialized = false;
|
||||
let rssTabInitialized = false;
|
||||
|
||||
let syncRequestInProgress = false;
|
||||
|
||||
|
@ -171,8 +172,20 @@ window.addEvent('load', function() {
|
|||
$("searchTabColumn").addClass("invisible");
|
||||
};
|
||||
|
||||
const buildRssTab = function() {
|
||||
new MochaUI.Column({
|
||||
id: 'rssTabColumn',
|
||||
placement: 'main',
|
||||
width: null
|
||||
});
|
||||
|
||||
// start off hidden
|
||||
$("rssTabColumn").addClass("invisible");
|
||||
};
|
||||
|
||||
buildTransfersTab();
|
||||
buildSearchTab();
|
||||
buildRssTab();
|
||||
MochaUI.initializeTabs('mainWindowTabsList');
|
||||
|
||||
setCategoryFilter = function(hash) {
|
||||
|
@ -275,12 +288,7 @@ window.addEvent('load', function() {
|
|||
|
||||
// After showing/hiding the toolbar + status bar
|
||||
let showSearchEngine = LocalPreferences.get('show_search_engine') !== "false";
|
||||
if (!showSearchEngine) {
|
||||
// uncheck menu option
|
||||
$('showSearchEngineLink').firstChild.style.opacity = '0';
|
||||
// hide tabs
|
||||
$('mainWindowTabs').addClass('invisible');
|
||||
}
|
||||
let showRssReader = LocalPreferences.get('show_rss_reader') !== "false";
|
||||
|
||||
// After Show Top Toolbar
|
||||
MochaUI.Desktop.setDesktopSize();
|
||||
|
@ -860,22 +868,48 @@ window.addEvent('load', function() {
|
|||
$('showSearchEngineLink').addEvent('click', function(e) {
|
||||
showSearchEngine = !showSearchEngine;
|
||||
LocalPreferences.set('show_search_engine', showSearchEngine.toString());
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
$('showRssReaderLink').addEvent('click', function(e) {
|
||||
showRssReader = !showRssReader;
|
||||
LocalPreferences.set('show_rss_reader', showRssReader.toString());
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
const updateTabDisplay = function() {
|
||||
if (showRssReader) {
|
||||
$('showRssReaderLink').firstChild.style.opacity = '1';
|
||||
$('mainWindowTabs').removeClass('invisible');
|
||||
$('rssTabLink').removeClass('invisible');
|
||||
if (!MochaUI.Panels.instances.RssPanel)
|
||||
addRssPanel();
|
||||
}
|
||||
else {
|
||||
$('showRssReaderLink').firstChild.style.opacity = '0';
|
||||
$('rssTabLink').addClass('invisible');
|
||||
if ($('rssTabLink').hasClass('selected'))
|
||||
$("transfersTabLink").click();
|
||||
}
|
||||
|
||||
if (showSearchEngine) {
|
||||
$('showSearchEngineLink').firstChild.style.opacity = '1';
|
||||
$('mainWindowTabs').removeClass('invisible');
|
||||
|
||||
addMainWindowTabsEventListener();
|
||||
$('searchTabLink').removeClass('invisible');
|
||||
if (!MochaUI.Panels.instances.SearchPanel)
|
||||
addSearchPanel();
|
||||
}
|
||||
else {
|
||||
$('showSearchEngineLink').firstChild.style.opacity = '0';
|
||||
$('mainWindowTabs').addClass('invisible');
|
||||
$("transfersTabLink").click();
|
||||
|
||||
removeMainWindowTabsEventListener();
|
||||
$('searchTabLink').addClass('invisible');
|
||||
if ($('searchTabLink').hasClass('selected'))
|
||||
$("transfersTabLink").click();
|
||||
}
|
||||
});
|
||||
|
||||
// display no tabs
|
||||
if (!showRssReader && !showSearchEngine)
|
||||
$('mainWindowTabs').addClass('invisible');
|
||||
};
|
||||
|
||||
$('StatisticsLink').addEvent('click', StatisticsLinkFN);
|
||||
|
||||
|
@ -890,6 +924,7 @@ window.addEvent('load', function() {
|
|||
syncData(100);
|
||||
|
||||
hideSearchTab();
|
||||
hideRssTab();
|
||||
};
|
||||
|
||||
const hideTransfersTab = function() {
|
||||
|
@ -908,6 +943,7 @@ window.addEvent('load', function() {
|
|||
$("searchTabColumn").removeClass("invisible");
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideRssTab();
|
||||
};
|
||||
|
||||
const hideSearchTab = function() {
|
||||
|
@ -915,14 +951,25 @@ window.addEvent('load', function() {
|
|||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
const addMainWindowTabsEventListener = function() {
|
||||
$('transfersTabLink').addEvent('click', showTransfersTab);
|
||||
$('searchTabLink').addEvent('click', showSearchTab);
|
||||
const showRssTab = function() {
|
||||
if (!rssTabInitialized) {
|
||||
window.qBittorrent.Rss.init();
|
||||
rssTabInitialized = true;
|
||||
}
|
||||
else {
|
||||
window.qBittorrent.Rss.load();
|
||||
}
|
||||
|
||||
$("rssTabColumn").removeClass("invisible");
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideSearchTab();
|
||||
};
|
||||
|
||||
const removeMainWindowTabsEventListener = function() {
|
||||
$('transfersTabLink').removeEvent('click', showTransfersTab);
|
||||
$('searchTabLink').removeEvent('click', showSearchTab);
|
||||
const hideRssTab = function() {
|
||||
$("rssTabColumn").addClass("invisible");
|
||||
window.qBittorrent.Rss.unload();
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
const addSearchPanel = function() {
|
||||
|
@ -944,6 +991,25 @@ window.addEvent('load', function() {
|
|||
});
|
||||
};
|
||||
|
||||
const addRssPanel = function() {
|
||||
new MochaUI.Panel({
|
||||
id: 'RssPanel',
|
||||
title: 'Rss',
|
||||
header: false,
|
||||
padding: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
},
|
||||
loadMethod: 'xhr',
|
||||
contentURL: 'views/rss.html',
|
||||
content: '',
|
||||
column: 'rssTabColumn',
|
||||
height: null
|
||||
});
|
||||
};
|
||||
|
||||
new MochaUI.Panel({
|
||||
id: 'transferList',
|
||||
title: 'Panel',
|
||||
|
@ -1081,10 +1147,10 @@ window.addEvent('load', function() {
|
|||
}
|
||||
});
|
||||
|
||||
if (showSearchEngine) {
|
||||
addMainWindowTabsEventListener();
|
||||
addSearchPanel();
|
||||
}
|
||||
$('transfersTabLink').addEvent('click', showTransfersTab);
|
||||
$('searchTabLink').addEvent('click', showSearchTab);
|
||||
$('rssTabLink').addEvent('click', showRssTab);
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
function registerMagnetHandler() {
|
||||
|
|
|
@ -39,7 +39,10 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
TorrentsTableContextMenu: TorrentsTableContextMenu,
|
||||
CategoriesFilterContextMenu: CategoriesFilterContextMenu,
|
||||
TagsFilterContextMenu: TagsFilterContextMenu,
|
||||
SearchPluginsTableContextMenu: SearchPluginsTableContextMenu
|
||||
SearchPluginsTableContextMenu: SearchPluginsTableContextMenu,
|
||||
RssFeedContextMenu: RssFeedContextMenu,
|
||||
RssArticleContextMenu: RssArticleContextMenu,
|
||||
RssDownloaderRuleContextMenu: RssDownloaderRuleContextMenu
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -538,5 +541,129 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
}
|
||||
});
|
||||
|
||||
const RssFeedContextMenu = new Class({
|
||||
Extends: ContextMenu,
|
||||
updateMenuItems: function() {
|
||||
let selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
|
||||
this.menu.getElement('a[href$=newSubscription]').parentNode.addClass('separator');
|
||||
switch (selectedRows.length) {
|
||||
case 0:
|
||||
// remove seperator on top of newSubscription entry to avoid double line
|
||||
this.menu.getElement('a[href$=newSubscription]').parentNode.removeClass('separator');
|
||||
// menu when nothing selected
|
||||
this.hideItem('update');
|
||||
this.hideItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.hideItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.showItem('newFolder');
|
||||
this.showItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
break;
|
||||
case 1:
|
||||
if (selectedRows[0] === 0) {
|
||||
// menu when "unread" feed selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.hideItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
}
|
||||
else if (window.qBittorrent.Rss.rssFeedTable.rows[selectedRows[0]].full_data.dataUid === '') {
|
||||
// menu when single folder selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.showItem('rename');
|
||||
this.showItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.showItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
}
|
||||
else {
|
||||
// menu when single feed selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.showItem('rename');
|
||||
this.showItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.showItem('copyFeedURL');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// menu when multiple items selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.showItem('delete');
|
||||
this.hideItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.showItem('copyFeedURL');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const RssArticleContextMenu = new Class({
|
||||
Extends: ContextMenu
|
||||
});
|
||||
|
||||
const RssDownloaderRuleContextMenu = new Class({
|
||||
Extends: ContextMenu,
|
||||
adjustMenuPosition: function(e) {
|
||||
this.updateMenuItems();
|
||||
|
||||
// draw the menu off-screen to know the menu dimensions
|
||||
this.menu.setStyles({
|
||||
left: '-999em',
|
||||
top: '-999em'
|
||||
});
|
||||
// position the menu
|
||||
let xPosMenu = e.page.x + this.options.offsets.x - $('rssdownloaderpage').offsetLeft;
|
||||
let yPosMenu = e.page.y + this.options.offsets.y - $('rssdownloaderpage').offsetTop;
|
||||
if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
|
||||
xPosMenu -= this.menu.offsetWidth;
|
||||
if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
|
||||
yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight;
|
||||
xPosMenu = Math.max(xPosMenu, 0);
|
||||
yPosMenu = Math.max(yPosMenu, 0);
|
||||
|
||||
this.menu.setStyles({
|
||||
left: xPosMenu,
|
||||
top: yPosMenu,
|
||||
position: 'absolute',
|
||||
'z-index': '2000'
|
||||
});
|
||||
},
|
||||
updateMenuItems: function() {
|
||||
let selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
|
||||
this.showItem('addRule');
|
||||
switch (selectedRows.length) {
|
||||
case 0:
|
||||
// menu when nothing selected
|
||||
this.hideItem('deleteRule');
|
||||
this.hideItem('renameRule');
|
||||
this.hideItem('clearDownloadedEpisodes');
|
||||
break;
|
||||
case 1:
|
||||
// menu when single item selected
|
||||
this.showItem('deleteRule');
|
||||
this.showItem('renameRule');
|
||||
this.showItem('clearDownloadedEpisodes');
|
||||
break;
|
||||
default:
|
||||
// menu when multiple items selected
|
||||
this.showItem('deleteRule');
|
||||
this.hideItem('renameRule');
|
||||
this.showItem('clearDownloadedEpisodes');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
|
|
@ -45,7 +45,12 @@ window.qBittorrent.DynamicTable = (function() {
|
|||
SearchResultsTable: SearchResultsTable,
|
||||
SearchPluginsTable: SearchPluginsTable,
|
||||
TorrentTrackersTable: TorrentTrackersTable,
|
||||
TorrentFilesTable: TorrentFilesTable
|
||||
TorrentFilesTable: TorrentFilesTable,
|
||||
RssFeedTable: RssFeedTable,
|
||||
RssArticleTable: RssArticleTable,
|
||||
RssDownloaderRulesTable: RssDownloaderRulesTable,
|
||||
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
|
||||
RssDownloaderArticlesTable: RssDownloaderArticlesTable
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1867,7 +1872,7 @@ window.qBittorrent.DynamicTable = (function() {
|
|||
if (tr.hasClass("invisible"))
|
||||
return;
|
||||
|
||||
if (addClass){
|
||||
if (addClass) {
|
||||
tr.addClass("alt");
|
||||
tr.removeClass("nonAlt");
|
||||
}
|
||||
|
@ -2000,6 +2005,492 @@ window.qBittorrent.DynamicTable = (function() {
|
|||
}
|
||||
});
|
||||
|
||||
const RssFeedTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
initColumns: function() {
|
||||
this.newColumn('state_icon', '', '', 30, true);
|
||||
this.newColumn('name', '', 'QBT_TR(RSS feeds)QBT_TR[CONTEXT=FeedListWidget]', -1, true);
|
||||
|
||||
this.columns['state_icon'].dataProperties[0] = '';
|
||||
|
||||
// map name row to "[name] ([unread])"
|
||||
this.columns['name'].dataProperties.push('unread');
|
||||
this.columns['name'].updateTd = function(td, row) {
|
||||
const name = this.getRowValue(row, 0);
|
||||
const unreadCount = this.getRowValue(row, 1);
|
||||
let value = name + ' (' + unreadCount + ')';
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
},
|
||||
setupHeaderMenu: function() {},
|
||||
setupHeaderEvents: function() {},
|
||||
getFilteredAndSortedRows: function() {
|
||||
return this.rows.getValues();
|
||||
},
|
||||
selectRow: function(rowId) {
|
||||
this.selectedRows.push(rowId);
|
||||
this.setRowClass();
|
||||
this.onSelectedRowChanged();
|
||||
|
||||
const rows = this.rows.getValues();
|
||||
let path = '';
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
if (rows[i].rowId === rowId) {
|
||||
path = rows[i].full_data.dataPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.qBittorrent.Rss.showRssFeed(path);
|
||||
},
|
||||
setupTr: function(tr) {
|
||||
tr.addEvent('dblclick', function(e) {
|
||||
if (this.rowId !== 0) {
|
||||
window.qBittorrent.Rss.moveItem(this._this.rows.get(this.rowId).full_data.dataPath);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateRow: function(tr, fullUpdate) {
|
||||
const row = this.rows.get(tr.rowId);
|
||||
const data = row[fullUpdate ? 'full_data' : 'data'];
|
||||
|
||||
const tds = tr.getElements('td');
|
||||
for (let i = 0; i < this.columns.length; ++i) {
|
||||
if (data.hasOwnProperty(this.columns[i].dataProperties[0]))
|
||||
this.columns[i].updateTd(tds[i], row);
|
||||
}
|
||||
row['data'] = {};
|
||||
tds[0].style.overflow = 'visible';
|
||||
let indentaion = row.full_data.indentaion;
|
||||
tds[0].style.paddingLeft = (indentaion * 32 + 4) + 'px';
|
||||
tds[1].style.paddingLeft = (indentaion * 32 + 4) + 'px';
|
||||
},
|
||||
updateIcons: function() {
|
||||
// state_icon
|
||||
this.rows.each(row => {
|
||||
let img_path;
|
||||
switch (row.full_data.status) {
|
||||
case 'default':
|
||||
img_path = 'icons/application-rss+xml.svg';
|
||||
break;
|
||||
case 'hasError':
|
||||
img_path = 'icons/unavailable.svg';
|
||||
break;
|
||||
case 'isLoading':
|
||||
img_path = 'images/spinner.gif';
|
||||
break;
|
||||
case 'unread':
|
||||
img_path = 'icons/mail-folder-inbox.svg';
|
||||
break;
|
||||
case 'isFolder':
|
||||
img_path = 'icons/folder-documents.svg';
|
||||
break;
|
||||
}
|
||||
let td;
|
||||
for (let i = 0; i < this.tableBody.rows.length; ++i) {
|
||||
if (this.tableBody.rows[i].rowId === row.rowId) {
|
||||
td = this.tableBody.rows[i].children[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (td.getChildren('img').length > 0) {
|
||||
const img = td.getChildren('img')[0];
|
||||
if (img.src.indexOf(img_path) < 0) {
|
||||
img.set('src', img_path);
|
||||
img.set('title', status);
|
||||
}
|
||||
}
|
||||
else {
|
||||
td.adopt(new Element('img', {
|
||||
'src': img_path,
|
||||
'class': 'stateIcon',
|
||||
'height': '22px',
|
||||
'width': '22px'
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
|
||||
const column = {};
|
||||
column['name'] = name;
|
||||
column['title'] = name;
|
||||
column['visible'] = defaultVisible;
|
||||
column['force_hide'] = false;
|
||||
column['caption'] = caption;
|
||||
column['style'] = style;
|
||||
if (defaultWidth !== -1) {
|
||||
column['width'] = defaultWidth;
|
||||
}
|
||||
|
||||
column['dataProperties'] = [name];
|
||||
column['getRowValue'] = function(row, pos) {
|
||||
if (pos === undefined)
|
||||
pos = 0;
|
||||
return row['full_data'][this.dataProperties[pos]];
|
||||
};
|
||||
column['compareRows'] = function(row1, row2) {
|
||||
if (this.getRowValue(row1) < this.getRowValue(row2))
|
||||
return -1;
|
||||
else if (this.getRowValue(row1) > this.getRowValue(row2))
|
||||
return 1;
|
||||
else return 0;
|
||||
};
|
||||
column['updateTd'] = function(td, row) {
|
||||
const value = this.getRowValue(row)
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
column['onResize'] = null;
|
||||
this.columns.push(column);
|
||||
this.columns[name] = column;
|
||||
|
||||
this.hiddenTableHeader.appendChild(new Element('th'));
|
||||
this.fixedTableHeader.appendChild(new Element('th'));
|
||||
},
|
||||
setupCommonEvents: function() {
|
||||
const scrollFn = function() {
|
||||
$(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px';
|
||||
}.bind(this);
|
||||
|
||||
$(this.dynamicTableDivId).addEvent('scroll', scrollFn);
|
||||
}
|
||||
});
|
||||
|
||||
const RssArticleTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
initColumns: function() {
|
||||
this.newColumn('name', '', 'QBT_TR(Torrents: (double-click to download))QBT_TR[CONTEXT=RSSWidget]', -1, true);
|
||||
},
|
||||
setupHeaderMenu: function() {},
|
||||
setupHeaderEvents: function() {},
|
||||
getFilteredAndSortedRows: function() {
|
||||
return this.rows.getValues();
|
||||
},
|
||||
selectRow: function(rowId) {
|
||||
this.selectedRows.push(rowId);
|
||||
this.setRowClass();
|
||||
this.onSelectedRowChanged();
|
||||
|
||||
const rows = this.rows.getValues();
|
||||
let articleId = '';
|
||||
let feedUid = '';
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
if (rows[i].rowId === rowId) {
|
||||
articleId = rows[i].full_data.dataId;
|
||||
feedUid = rows[i].full_data.feedUid;
|
||||
this.tableBody.rows[rows[i].rowId].removeClass('unreadArticle');
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.qBittorrent.Rss.showDetails(feedUid, articleId);
|
||||
},
|
||||
setupTr: function(tr) {
|
||||
tr.addEvent('dblclick', function(e) {
|
||||
showDownloadPage([this._this.rows.get(this.rowId).full_data.torrentURL]);
|
||||
return true;
|
||||
});
|
||||
tr.addClass('torrentsTableContextMenuTarget');
|
||||
},
|
||||
updateRow: function(tr, fullUpdate) {
|
||||
const row = this.rows.get(tr.rowId);
|
||||
const data = row[fullUpdate ? 'full_data' : 'data'];
|
||||
if (!row.full_data.isRead)
|
||||
tr.addClass('unreadArticle');
|
||||
else
|
||||
tr.removeClass('unreadArticle');
|
||||
|
||||
const tds = tr.getElements('td');
|
||||
for (let i = 0; i < this.columns.length; ++i) {
|
||||
if (data.hasOwnProperty(this.columns[i].dataProperties[0]))
|
||||
this.columns[i].updateTd(tds[i], row);
|
||||
}
|
||||
row['data'] = {};
|
||||
},
|
||||
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
|
||||
const column = {};
|
||||
column['name'] = name;
|
||||
column['title'] = name;
|
||||
column['visible'] = defaultVisible;
|
||||
column['force_hide'] = false;
|
||||
column['caption'] = caption;
|
||||
column['style'] = style;
|
||||
if (defaultWidth !== -1) {
|
||||
column['width'] = defaultWidth;
|
||||
}
|
||||
|
||||
column['dataProperties'] = [name];
|
||||
column['getRowValue'] = function(row, pos) {
|
||||
if (pos === undefined)
|
||||
pos = 0;
|
||||
return row['full_data'][this.dataProperties[pos]];
|
||||
};
|
||||
column['compareRows'] = function(row1, row2) {
|
||||
if (this.getRowValue(row1) < this.getRowValue(row2))
|
||||
return -1;
|
||||
else if (this.getRowValue(row1) > this.getRowValue(row2))
|
||||
return 1;
|
||||
else return 0;
|
||||
};
|
||||
column['updateTd'] = function(td, row) {
|
||||
const value = this.getRowValue(row)
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
column['onResize'] = null;
|
||||
this.columns.push(column);
|
||||
this.columns[name] = column;
|
||||
|
||||
this.hiddenTableHeader.appendChild(new Element('th'));
|
||||
this.fixedTableHeader.appendChild(new Element('th'));
|
||||
},
|
||||
setupCommonEvents: function() {
|
||||
const scrollFn = function() {
|
||||
$(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px';
|
||||
}.bind(this);
|
||||
|
||||
$(this.dynamicTableDivId).addEvent('scroll', scrollFn);
|
||||
}
|
||||
});
|
||||
|
||||
const RssDownloaderRulesTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
initColumns: function() {
|
||||
this.newColumn('checked', '', '', 30, true);
|
||||
this.newColumn('name', '', '', -1, true);
|
||||
|
||||
this.columns['checked'].updateTd = function(td, row) {
|
||||
if ($('cbRssDlRule' + row.rowId) === null) {
|
||||
const checkbox = new Element('input');
|
||||
checkbox.set('type', 'checkbox');
|
||||
checkbox.set('id', 'cbRssDlRule' + row.rowId);
|
||||
checkbox.checked = row.full_data.checked;
|
||||
|
||||
checkbox.addEvent('click', function(e) {
|
||||
window.qBittorrent.RssDownloader.rssDownloaderRulesTable.updateRowData({
|
||||
rowId: row.rowId,
|
||||
checked: this.checked
|
||||
});
|
||||
window.qBittorrent.RssDownloader.modifyRuleState(row.full_data.name, 'enabled', this.checked);
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
td.append(checkbox);
|
||||
}
|
||||
else {
|
||||
$('cbRssDlRule' + row.rowId).checked = row.full_data.checked;
|
||||
}
|
||||
};
|
||||
},
|
||||
setupHeaderMenu: function() {},
|
||||
setupHeaderEvents: function() {},
|
||||
getFilteredAndSortedRows: function() {
|
||||
return this.rows.getValues();
|
||||
},
|
||||
setupTr: function(tr) {
|
||||
tr.addEvent('dblclick', function(e) {
|
||||
window.qBittorrent.RssDownloader.renameRule(this._this.rows.get(this.rowId).full_data.name);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
|
||||
const column = {};
|
||||
column['name'] = name;
|
||||
column['title'] = name;
|
||||
column['visible'] = defaultVisible;
|
||||
column['force_hide'] = false;
|
||||
column['caption'] = caption;
|
||||
column['style'] = style;
|
||||
if (defaultWidth !== -1) {
|
||||
column['width'] = defaultWidth;
|
||||
}
|
||||
|
||||
column['dataProperties'] = [name];
|
||||
column['getRowValue'] = function(row, pos) {
|
||||
if (pos === undefined)
|
||||
pos = 0;
|
||||
return row['full_data'][this.dataProperties[pos]];
|
||||
};
|
||||
column['compareRows'] = function(row1, row2) {
|
||||
if (this.getRowValue(row1) < this.getRowValue(row2))
|
||||
return -1;
|
||||
else if (this.getRowValue(row1) > this.getRowValue(row2))
|
||||
return 1;
|
||||
else return 0;
|
||||
};
|
||||
column['updateTd'] = function(td, row) {
|
||||
const value = this.getRowValue(row)
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
column['onResize'] = null;
|
||||
this.columns.push(column);
|
||||
this.columns[name] = column;
|
||||
|
||||
this.hiddenTableHeader.appendChild(new Element('th'));
|
||||
this.fixedTableHeader.appendChild(new Element('th'));
|
||||
},
|
||||
selectRow: function(rowId) {
|
||||
this.selectedRows.push(rowId);
|
||||
this.setRowClass();
|
||||
this.onSelectedRowChanged();
|
||||
|
||||
const rows = this.rows.getValues();
|
||||
let name = '';
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
if (rows[i].rowId === rowId) {
|
||||
name = rows[i].full_data.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.qBittorrent.RssDownloader.showRule(name);
|
||||
}
|
||||
});
|
||||
|
||||
const RssDownloaderFeedSelectionTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
initColumns: function() {
|
||||
this.newColumn('checked', '', '', 30, true);
|
||||
this.newColumn('name', '', '', -1, true);
|
||||
|
||||
this.columns['checked'].updateTd = function(td, row) {
|
||||
if ($('cbRssDlFeed' + row.rowId) === null) {
|
||||
const checkbox = new Element('input');
|
||||
checkbox.set('type', 'checkbox');
|
||||
checkbox.set('id', 'cbRssDlFeed' + row.rowId);
|
||||
checkbox.checked = row.full_data.checked;
|
||||
|
||||
checkbox.addEvent('click', function(e) {
|
||||
window.qBittorrent.RssDownloader.rssDownloaderFeedSelectionTable.updateRowData({
|
||||
rowId: row.rowId,
|
||||
checked: this.checked
|
||||
});
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
td.append(checkbox);
|
||||
}
|
||||
else {
|
||||
$('cbRssDlFeed' + row.rowId).checked = row.full_data.checked;
|
||||
}
|
||||
};
|
||||
},
|
||||
setupHeaderMenu: function() {},
|
||||
setupHeaderEvents: function() {},
|
||||
getFilteredAndSortedRows: function() {
|
||||
return this.rows.getValues();
|
||||
},
|
||||
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
|
||||
const column = {};
|
||||
column['name'] = name;
|
||||
column['title'] = name;
|
||||
column['visible'] = defaultVisible;
|
||||
column['force_hide'] = false;
|
||||
column['caption'] = caption;
|
||||
column['style'] = style;
|
||||
if (defaultWidth !== -1) {
|
||||
column['width'] = defaultWidth;
|
||||
}
|
||||
|
||||
column['dataProperties'] = [name];
|
||||
column['getRowValue'] = function(row, pos) {
|
||||
if (pos === undefined)
|
||||
pos = 0;
|
||||
return row['full_data'][this.dataProperties[pos]];
|
||||
};
|
||||
column['compareRows'] = function(row1, row2) {
|
||||
if (this.getRowValue(row1) < this.getRowValue(row2))
|
||||
return -1;
|
||||
else if (this.getRowValue(row1) > this.getRowValue(row2))
|
||||
return 1;
|
||||
else return 0;
|
||||
};
|
||||
column['updateTd'] = function(td, row) {
|
||||
const value = this.getRowValue(row)
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
column['onResize'] = null;
|
||||
this.columns.push(column);
|
||||
this.columns[name] = column;
|
||||
|
||||
this.hiddenTableHeader.appendChild(new Element('th'));
|
||||
this.fixedTableHeader.appendChild(new Element('th'));
|
||||
},
|
||||
selectRow: function() {}
|
||||
});
|
||||
|
||||
const RssDownloaderArticlesTable = new Class({
|
||||
Extends: DynamicTable,
|
||||
initColumns: function() {
|
||||
this.newColumn('name', '', '', -1, true);
|
||||
},
|
||||
setupHeaderMenu: function() {},
|
||||
setupHeaderEvents: function() {},
|
||||
getFilteredAndSortedRows: function() {
|
||||
return this.rows.getValues();
|
||||
},
|
||||
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
|
||||
const column = {};
|
||||
column['name'] = name;
|
||||
column['title'] = name;
|
||||
column['visible'] = defaultVisible;
|
||||
column['force_hide'] = false;
|
||||
column['caption'] = caption;
|
||||
column['style'] = style;
|
||||
if (defaultWidth !== -1) {
|
||||
column['width'] = defaultWidth;
|
||||
}
|
||||
|
||||
column['dataProperties'] = [name];
|
||||
column['getRowValue'] = function(row, pos) {
|
||||
if (pos === undefined)
|
||||
pos = 0;
|
||||
return row['full_data'][this.dataProperties[pos]];
|
||||
};
|
||||
column['compareRows'] = function(row1, row2) {
|
||||
if (this.getRowValue(row1) < this.getRowValue(row2))
|
||||
return -1;
|
||||
else if (this.getRowValue(row1) > this.getRowValue(row2))
|
||||
return 1;
|
||||
else return 0;
|
||||
};
|
||||
column['updateTd'] = function(td, row) {
|
||||
const value = this.getRowValue(row)
|
||||
td.set('text', value);
|
||||
td.set('title', value);
|
||||
};
|
||||
column['onResize'] = null;
|
||||
this.columns.push(column);
|
||||
this.columns[name] = column;
|
||||
|
||||
this.hiddenTableHeader.appendChild(new Element('th'));
|
||||
this.fixedTableHeader.appendChild(new Element('th'));
|
||||
},
|
||||
selectRow: function() {},
|
||||
updateRow: function(tr, fullUpdate) {
|
||||
const row = this.rows.get(tr.rowId);
|
||||
const data = row[fullUpdate ? 'full_data' : 'data'];
|
||||
|
||||
if (row.full_data.isFeed) {
|
||||
tr.addClass('articleTableFeed');
|
||||
tr.removeClass('articleTableArticle');
|
||||
}
|
||||
else {
|
||||
tr.removeClass('articleTableFeed');
|
||||
tr.addClass('articleTableArticle');
|
||||
}
|
||||
|
||||
const tds = tr.getElements('td');
|
||||
for (let i = 0; i < this.columns.length; ++i) {
|
||||
if (data.hasOwnProperty(this.columns[i].dataProperties[0]))
|
||||
this.columns[i].updateTd(tds[i], row);
|
||||
}
|
||||
row['data'] = {};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
||||
|
|
|
@ -595,6 +595,54 @@
|
|||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div id="RSSTab" class="PrefTab invisible">
|
||||
<fieldset class="settings">
|
||||
<legend>QBT_TR(RSS Reader)QBT_TR[CONTEXT=OptionsDialog]</legend>
|
||||
<div class="formRow">
|
||||
<input type="checkbox" id="enable_fetching_rss_feeds_checkbox" />
|
||||
<label for="enable_fetching_rss_feeds_checkbox">QBT_TR(Enable fetching RSS feeds)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="feed_refresh_interval">QBT_TR(Feeds refresh interval:)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="feed_refresh_interval" style="width: 4em;" /> QBT_TR( min)QBT_TR[CONTEXT=OptionsDialog]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="maximum_article_number">QBT_TR(Maximum number of articles per feed:)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="maximum_article_number" style="width: 4em;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="settings">
|
||||
<legend>QBT_TR(RSS Torrent Auto Downloader)QBT_TR[CONTEXT=OptionsDialog]</legend>
|
||||
<div class="formRow">
|
||||
<input type="checkbox" id="enable_auto_downloading_rss_torrents_checkbox" />
|
||||
<label for="enable_auto_downloading_rss_torrents_checkbox">QBT_TR(Enable auto downloading of RSS torrents)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</div>
|
||||
<button style="margin: 0 1em; width: calc(100% - 2.2em)" onclick="window.qBittorrent.Rss.openRssDownloader();">QBT_TR(Edit auto downloading rules...)QBT_TR[CONTEXT=OptionsDialog]</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="settings">
|
||||
<legend>QBT_TR(RSS Smart Episode Filter)QBT_TR[CONTEXT=OptionsDialog]</legend>
|
||||
<div class="formRow">
|
||||
<input type="checkbox" id="downlock_repack_proper_episodes" />
|
||||
|
||||
<label for="downlock_repack_proper_episodes">QBT_TR(Download REPACK/PROPER episodes)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</div>
|
||||
<label for="rss_filter_textarea">QBT_TR(Filters:)QBT_TR[CONTEXT=OptionsDialog]</label><br>
|
||||
<textarea id="rss_filter_textarea" rows="6" cols="70"></textarea>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div id="WebUITab" class="PrefTab invisible">
|
||||
<fieldset class="settings">
|
||||
<legend>QBT_TR(Language)QBT_TR[CONTEXT=OptionsDialog]</legend>
|
||||
|
@ -1723,6 +1771,14 @@
|
|||
$('add_trackers_textarea').setProperty('value', pref.add_trackers);
|
||||
updateAddTrackersEnabled();
|
||||
|
||||
// RSS Tab
|
||||
$('enable_fetching_rss_feeds_checkbox').setProperty('checked', pref.rss_processing_enabled);
|
||||
$('feed_refresh_interval').setProperty('value', pref.rss_refresh_interval);
|
||||
$('maximum_article_number').setProperty('value', pref.rss_max_articles_per_feed);
|
||||
$('enable_auto_downloading_rss_torrents_checkbox').setProperty('checked', pref.rss_auto_downloading_enabled);
|
||||
$('downlock_repack_proper_episodes').setProperty('checked', pref.rss_download_repack_proper_episodes);
|
||||
$('rss_filter_textarea').setProperty('value', pref.rss_smart_episode_filters);
|
||||
|
||||
// Web UI tab
|
||||
// Language
|
||||
$('locale_select').setProperty('value', pref.locale);
|
||||
|
@ -2077,6 +2133,14 @@
|
|||
settings.set('add_trackers_enabled', $('add_trackers_checkbox').getProperty('checked'));
|
||||
settings.set('add_trackers', $('add_trackers_textarea').getProperty('value'));
|
||||
|
||||
// RSS Tab
|
||||
settings.set('rss_processing_enabled', $('enable_fetching_rss_feeds_checkbox').getProperty('checked'));
|
||||
settings.set('rss_refresh_interval', $('feed_refresh_interval').getProperty('value'));
|
||||
settings.set('rss_max_articles_per_feed', $('maximum_article_number').getProperty('value'));
|
||||
settings.set('rss_auto_downloading_enabled', $('enable_auto_downloading_rss_torrents_checkbox').getProperty('checked'));
|
||||
settings.set('rss_download_repack_proper_episodes', $('downlock_repack_proper_episodes').getProperty('checked'));
|
||||
settings.set('rss_smart_episode_filters', $('rss_filter_textarea').getProperty('value'));
|
||||
|
||||
// Web UI tab
|
||||
// Language
|
||||
settings.set('locale', $('locale_select').getProperty('value'));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<li id="PrefConnectionLink"><a>QBT_TR(Connection)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
<li id="PrefSpeedLink"><a>QBT_TR(Speed)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
<li id="PrefBittorrentLink"><a>QBT_TR(BitTorrent)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
<li id="PrefRSSLink"><a>QBT_TR(RSS)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
<li id="PrefWebUILink"><a>QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
<li id="PrefAdvancedLink"><a>QBT_TR(Advanced)QBT_TR[CONTEXT=OptionsDialog]</a></li>
|
||||
</ul>
|
||||
|
@ -34,6 +35,10 @@
|
|||
$$('.PrefTab').addClass('invisible');
|
||||
$('BittorrentTab').removeClass('invisible');
|
||||
});
|
||||
$('PrefRSSLink').addEvent('click', function(e) {
|
||||
$$('.PrefTab').addClass('invisible');
|
||||
$('RSSTab').removeClass('invisible');
|
||||
});
|
||||
$('PrefWebUILink').addEvent('click', function(e) {
|
||||
$$('.PrefTab').addClass('invisible');
|
||||
$('WebUITab').removeClass('invisible');
|
||||
|
|
823
src/webui/www/private/views/rss.html
Normal file
823
src/webui/www/private/views/rss.html
Normal file
|
@ -0,0 +1,823 @@
|
|||
<style>
|
||||
#rssView {
|
||||
padding: 20px 20px 0 20px;
|
||||
height: calc(100% - 20px);
|
||||
}
|
||||
|
||||
#rssContentView {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#rssFeedFixedHeaderDiv .dynamicTableHeader, #rssArticleFixedHeaderDiv .dynamicTableHeader {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.unreadArticle {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
#rssFetchingDisabled {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#centerRssColumn {
|
||||
margin: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
#leftRssColumn, #centerRssColumn, #rightRssColumn {
|
||||
float: left;
|
||||
/* should be 20 px but due to rounding differences some browsers don't render that properly */
|
||||
width: calc(calc(100% - 21px) / 3);
|
||||
border: none;
|
||||
}
|
||||
|
||||
#rightRssColumn {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#rssFeedTableDiv, #rssArticleTableDiv {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
|
||||
#rssTorrentDetailsName {
|
||||
background-color: #678db2;
|
||||
padding: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#rssTorrentDetailsDate {
|
||||
background-color: #EFEFEF;
|
||||
}
|
||||
|
||||
#rssDetailsView {
|
||||
height: calc(100vh - 135px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#rssButtonBar {
|
||||
overflow: hidden;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#rssContentView table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="rssView">
|
||||
<div id="rssTopBar">
|
||||
<div id="rssFetchingDisabled" class="invisible">
|
||||
QBT_TR(Fetching of RSS feeds is disabled now! You can enable it in application settings.)QBT_TR[CONTEXT=RSSWidget]
|
||||
</div>
|
||||
<div id="rssButtonBar">
|
||||
<button id="newSubscriptionButton" onclick="qBittorrent.Rss.addRSSFeed()">QBT_TR(New subscription)QBT_TR[CONTEXT=RSSWidget]</button>
|
||||
<button id="markReadButton" onclick="qBittorrent.Rss.markSelectedAsRead()">QBT_TR(Mark items read)QBT_TR[CONTEXT=RSSWidget]</button>
|
||||
<button id="updateAllButton" onclick="qBittorrent.Rss.refreshAllFeeds()">QBT_TR(Update all)QBT_TR[CONTEXT=RSSWidget]</button>
|
||||
|
||||
<button id="rssDownloaderButton" class="alignRight" onclick="qBittorrent.Rss.openRssDownloader()">QBT_TR(RSS Downloader...)QBT_TR[CONTEXT=RSSWidget]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rssContentView">
|
||||
<div id="leftRssColumn">
|
||||
<div id="rssFeedFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div id="rssFeedTableDiv" class="dynamicTableDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="centerRssColumn">
|
||||
<div id="rssArticleFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div id="rssArticleTableDiv" class="dynamicTableDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rightRssColumn">
|
||||
<div id="rssDetailsView"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="rssFeedMenu" class="contextMenu">
|
||||
<li><a href="#update"><img src="icons/view-refresh.svg" alt="QBT_TR(Update)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Update)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li><a href="#markRead"><img src="icons/mail-mark-read.svg" alt="QBT_TR(Mark items read)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Mark items read)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li class="separator"><a href="#rename"><img src="icons/edit-rename.svg" alt="QBT_TR(Rename...)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Rename...)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li><a href="#delete"><img src="icons/edit-delete.svg" alt="QBT_TR(Delete)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Delete)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
|
||||
<li class="separator"><a href="#newSubscription"><img src="icons/document-new.svg" alt="QBT_TR(New subscription...)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(New subscription...)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li><a href="#newFolder"><img src="icons/folder-new.svg" alt="QBT_TR(New folder...)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(New folder...)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li class="separator"><a href="#updateAll"><img src="icons/view-refresh.svg" alt="QBT_TR(Update all feeds)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Update all feeds)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
|
||||
<li class="separator"><a href="#copyFeedURL" id="CopyFeedURL"><img src="icons/edit-copy.svg" alt="QBT_TR(Copy feed URL)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Copy feed URL)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
</ul>
|
||||
|
||||
<ul id="rssArticleMenu" class="contextMenu">
|
||||
<li><a href="#Download"><img src="icons/download.svg" alt="QBT_TR(Download torrent)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Download torrent)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
<li><a href="#OpenNews"><img src="icons/application-x-mswinurl.svg" alt="QBT_TR(Open news URL)QBT_TR[CONTEXT=RSSWidget]" /> QBT_TR(Open news URL)QBT_TR[CONTEXT=RSSWidget]</a></li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
let serverSyncRssDataInterval = 1500;
|
||||
|
||||
window.qBittorrent.Rss = (() => {
|
||||
const exports = () => {
|
||||
return {
|
||||
init: init,
|
||||
unload: unload,
|
||||
load: load,
|
||||
showRssFeed: showRssFeed,
|
||||
showDetails: showDetails,
|
||||
updateRssFeedList: updateRssFeedList,
|
||||
refreshAllFeeds: refreshAllFeeds,
|
||||
moveItem: moveItem,
|
||||
addRSSFeed: addRSSFeed,
|
||||
markSelectedAsRead : markSelectedAsRead,
|
||||
openRssDownloader: openRssDownloader,
|
||||
rssFeedTable: rssFeedTable
|
||||
};
|
||||
};
|
||||
|
||||
let feedData = {};
|
||||
let pathByFeedId = new Map();;
|
||||
let feedRefreshTimer;
|
||||
let rssFeedTable = new window.qBittorrent.DynamicTable.RssFeedTable();
|
||||
let rssArticleTable = new window.qBittorrent.DynamicTable.RssArticleTable();
|
||||
|
||||
const init = () => {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/app/preferences',
|
||||
method: 'get',
|
||||
noCache: true,
|
||||
onFailure: () => {
|
||||
alert('Could not contact qBittorrent');
|
||||
},
|
||||
onSuccess: (pref) => {
|
||||
if (!pref.rss_processing_enabled)
|
||||
$('rssFetchingDisabled').removeClass('invisible');
|
||||
|
||||
// recalculate heights
|
||||
let nonPageHeight = $('rssTopBar').getBoundingClientRect().height +
|
||||
$('desktopHeader').getBoundingClientRect().height +
|
||||
$('desktopFooterWrapper').getBoundingClientRect().height + 20;
|
||||
$('rssDetailsView').style.height = 'calc(100vh - ' + nonPageHeight + 'px)';
|
||||
|
||||
let nonTableHeight = nonPageHeight + $('rssFeedFixedHeaderDiv').getBoundingClientRect().height;
|
||||
|
||||
$('rssFeedTableDiv').style.height = 'calc(100vh - ' + nonTableHeight + 'px)';
|
||||
$('rssArticleTableDiv').style.height = 'calc(100vh - ' + nonTableHeight + 'px)';
|
||||
|
||||
$('rssContentView').style.height = 'calc(100% - ' + $('rssTopBar').getBoundingClientRect().height + 'px)';
|
||||
}
|
||||
}).send();
|
||||
|
||||
const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({
|
||||
targets: '.rssFeedContextMenuTarget',
|
||||
menu: 'rssFeedMenu',
|
||||
actions: {
|
||||
update: (el) => {
|
||||
let feedsToUpdate = new Set();
|
||||
rssFeedTable.selectedRows.each((rowId) => {
|
||||
let selectedPath = rssFeedTable.rows[rowId].full_data.dataPath;
|
||||
rssFeedTable.rows.filter((row) => row.full_data.dataPath.slice(0, selectedPath.length) === selectedPath)
|
||||
.filter((row) => row.full_data.dataUid !== '')
|
||||
.each((row) => feedsToUpdate.add(row));
|
||||
});
|
||||
feedsToUpdate.forEach((feed) => refreshFeed(feed.full_data.dataUid))
|
||||
},
|
||||
markRead: markSelectedAsRead,
|
||||
rename: (el) => {
|
||||
moveItem(rssFeedTable.rows[rssFeedTable.selectedRows[0]].full_data.dataPath);
|
||||
},
|
||||
delete: (el) => {
|
||||
let selectedDatapaths = rssFeedTable.selectedRows
|
||||
.filter((e) => e !== 0)
|
||||
.map((sRow) => rssFeedTable.rows[sRow].full_data.dataPath);
|
||||
// filter children
|
||||
let reducedDatapaths = selectedDatapaths.filter((path) =>
|
||||
selectedDatapaths.filter((innerPath) => path.slice(0, innerPath.length) === innerPath).length === 1
|
||||
);
|
||||
removeItem(reducedDatapaths);
|
||||
},
|
||||
newSubscription: addRSSFeed,
|
||||
newFolder: addFolder,
|
||||
updateAll: refreshAllFeeds
|
||||
},
|
||||
offsets: {
|
||||
x: -16,
|
||||
y: -57
|
||||
}
|
||||
});
|
||||
|
||||
rssFeedContextMenu.addTarget($('rssFeedTableDiv'));
|
||||
// deselect feed when clicking on empty part of table
|
||||
$('rssFeedTableDiv').addEventListener('click', (e) => {
|
||||
rssFeedTable.deselectAll();
|
||||
rssFeedTable.deselectRow();
|
||||
});
|
||||
$('rssFeedTableDiv').addEventListener('contextmenu', (e) => {
|
||||
if (e.toElement.nodeName === 'DIV') {
|
||||
rssFeedTable.deselectAll();
|
||||
rssFeedTable.deselectRow();
|
||||
rssFeedContextMenu.updateMenuItems();
|
||||
}
|
||||
});
|
||||
|
||||
new ClipboardJS('#CopyFeedURL', {
|
||||
text: () => {
|
||||
let joined = '';
|
||||
rssFeedTable.selectedRows
|
||||
.filter((row) => rssFeedTable.rows[row].full_data.dataUid !== '')
|
||||
.each((row) => joined += rssFeedTable.rows[row].full_data.dataUrl + '\n');
|
||||
return joined.slice(0, -1);
|
||||
}
|
||||
});
|
||||
rssFeedTable.setup('rssFeedTableDiv', 'rssFeedFixedHeaderDiv', rssFeedContextMenu);
|
||||
|
||||
|
||||
const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({
|
||||
targets: '.rssArticleElement',
|
||||
menu: 'rssArticleMenu',
|
||||
actions: {
|
||||
Download: (el) => {
|
||||
let dlString = '';
|
||||
rssArticleTable.selectedRows.each((row) => {
|
||||
dlString += rssArticleTable.rows[row].full_data.torrentURL + '\n';
|
||||
});
|
||||
showDownloadPage([dlString]);
|
||||
},
|
||||
OpenNews: (el) => {
|
||||
rssArticleTable.selectedRows.each((row) => {
|
||||
window.open(rssArticleTable.rows[row].full_data.link);
|
||||
});
|
||||
}
|
||||
},
|
||||
offsets: {
|
||||
x: -16,
|
||||
y: -57
|
||||
}
|
||||
});
|
||||
rssArticleTable.setup('rssArticleTableDiv', 'rssArticleFixedHeaderDiv', rssArticleContextMenu);
|
||||
updateRssFeedList();
|
||||
load();
|
||||
};
|
||||
|
||||
const unload = () => {
|
||||
clearInterval(feedRefreshTimer);
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
feedRefreshTimer = setInterval(updateRssFeedList, serverSyncRssDataInterval);
|
||||
}
|
||||
|
||||
const addRSSFeed = () => {
|
||||
let path = '';
|
||||
if (rssFeedTable.selectedRows.length !== 0) {
|
||||
let row = rssFeedTable.rows[rssFeedTable.selectedRows[0]];
|
||||
if (row.full_data.dataUid === '') {
|
||||
path = row.full_data.dataPath;
|
||||
}
|
||||
else {
|
||||
let lastIndex = row.full_data.dataPath.lastIndexOf('\\');
|
||||
if (lastIndex !== -1)
|
||||
path = row.full_data.dataPath.slice(0, lastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
new MochaUI.Window({
|
||||
id: 'newFeed',
|
||||
title: 'QBT_TR(Please type a RSS feed URL)QBT_TR[CONTEXT=RSSWidget]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newfeed.html?path=' + encodeURIComponent(path),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 100
|
||||
});
|
||||
};
|
||||
|
||||
const addFolder = () => {
|
||||
let path = '';
|
||||
if (rssFeedTable.selectedRows.length !== 0) {
|
||||
let row = rssFeedTable.rows[rssFeedTable.selectedRows[0]];
|
||||
if (row.full_data.dataUid === '') {
|
||||
path = row.full_data.dataPath;
|
||||
}
|
||||
else {
|
||||
let lastIndex = row.full_data.dataPath.lastIndexOf('\\');
|
||||
if (lastIndex !== -1)
|
||||
path = row.full_data.dataPath.slice(0, lastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
new MochaUI.Window({
|
||||
id: 'newFolder',
|
||||
title: 'QBT_TR(Please choose a folder name)QBT_TR[CONTEXT=RSSWidget]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newfolder.html?path=' + encodeURIComponent(path),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 100
|
||||
});
|
||||
}
|
||||
|
||||
const showRssFeed = (path) => {
|
||||
rssArticleTable.clear();
|
||||
let rowCount = 0;
|
||||
|
||||
let childFeeds = new Set();
|
||||
rssFeedTable.rows.filter((row) => row.full_data.dataPath.slice(0, path.length) === path)
|
||||
.filter((row) => row.full_data.dataUid !== '')
|
||||
.each((row) => childFeeds.add(row.full_data.dataUid));
|
||||
|
||||
let visibleArticles = [];
|
||||
for (let feedEntry in feedData) {
|
||||
if (childFeeds.has(feedEntry))
|
||||
visibleArticles.append(feedData[feedEntry]
|
||||
.map((a) => {
|
||||
a.feedUid = feedEntry;
|
||||
return a;
|
||||
}));
|
||||
}
|
||||
//filter read articles if "Unread" feed is selected
|
||||
if (path === '')
|
||||
visibleArticles = visibleArticles.filter((a) => !a.isRead);
|
||||
|
||||
visibleArticles.sort((e1, e2) => new Date(e2.date) - new Date(e1.date))
|
||||
.each((torrentEntry) => {
|
||||
rssArticleTable.updateRowData({
|
||||
rowId: rowCount++,
|
||||
name: torrentEntry.title,
|
||||
link: torrentEntry.link,
|
||||
torrentURL: torrentEntry.torrentURL,
|
||||
feedUid: torrentEntry.feedUid,
|
||||
dataId: torrentEntry.id,
|
||||
isRead: torrentEntry.isRead
|
||||
});
|
||||
});
|
||||
|
||||
$('rssDetailsView').empty();
|
||||
rssArticleTable.updateTable(false);
|
||||
};
|
||||
|
||||
const showDetails = (feedUid, articleID) => {
|
||||
markArticleAsRead(pathByFeedId.get(feedUid), articleID);
|
||||
$('rssDetailsView').empty();
|
||||
let article = feedData[feedUid].filter((article) => article.id === articleID)[0];
|
||||
if (article) {
|
||||
$('rssDetailsView').append((() => {
|
||||
let torrentName = document.createElement('p');
|
||||
torrentName.innerText = article.title;
|
||||
torrentName.setAttribute('id', 'rssTorrentDetailsName');
|
||||
return torrentName;
|
||||
})());
|
||||
$('rssDetailsView').append((() => {
|
||||
let torrentDate = document.createElement('div');
|
||||
torrentDate.setAttribute('id', 'rssTorrentDetailsDate');
|
||||
|
||||
let torrentDateDesc = document.createElement('b');
|
||||
torrentDateDesc.innerText = 'QBT_TR(Date: )QBT_TR[CONTEXT=RSSWidget]';
|
||||
torrentDate.append(torrentDateDesc);
|
||||
|
||||
let torrentDateData = document.createElement('span');
|
||||
torrentDateData.innerText = new Date(article.date).toLocaleString();
|
||||
torrentDate.append(torrentDateData);
|
||||
|
||||
return torrentDate;
|
||||
})());
|
||||
// Strip script before interpreting html
|
||||
let torrentDescription = document.createRange().createContextualFragment(
|
||||
'<div id="rssTorrentDetailsDescription">' + article.description.stripScripts() + '</div>');
|
||||
|
||||
$('rssDetailsView').append(torrentDescription);
|
||||
}
|
||||
};
|
||||
|
||||
const updateRssFeedList = () => {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/rss/items',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
withData: true
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
// flatten folder structure
|
||||
let flattenedResp = [];
|
||||
let recFlatten = (current, name = '', depth = 0, fullName = '') => {
|
||||
for (let child in current) {
|
||||
let currentFullName = fullName ? (fullName + '\\' + child) : child;
|
||||
if (current[child].uid !== undefined) {
|
||||
current[child].name = child;
|
||||
current[child].isFolder = false;
|
||||
current[child].depth = depth;
|
||||
current[child].fullName = currentFullName;
|
||||
flattenedResp.push(current[child]);
|
||||
}
|
||||
else {
|
||||
flattenedResp.push({
|
||||
name: child,
|
||||
isFolder: true,
|
||||
depth: depth,
|
||||
fullName: currentFullName
|
||||
});
|
||||
recFlatten(current[child], child, depth + 1, currentFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
recFlatten(response);
|
||||
|
||||
// check if rows matche flattend response
|
||||
let match = false;
|
||||
if (rssFeedTable.rows.getLength() - 1 === flattenedResp.length) {
|
||||
match = true;
|
||||
for (let i = 0; i < flattenedResp.length; ++i) {
|
||||
if ((flattenedResp[i].uid ? flattenedResp[i].uid : '') !== rssFeedTable.rows[i + 1].full_data.dataUid ||
|
||||
flattenedResp[i].fullName !== rssFeedTable.rows[i + 1].full_data.dataPath) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// partial refresh
|
||||
// update status
|
||||
let statusDiffers = false;
|
||||
for (let i = 0; i < flattenedResp.length; ++i) {
|
||||
let oldStatus = rssFeedTable.rows[i + 1].full_data.status;
|
||||
let status = 'default';
|
||||
if (flattenedResp[i].hasError)
|
||||
status = 'hasError';
|
||||
if (flattenedResp[i].isLoading)
|
||||
status = 'isLoading';
|
||||
if (flattenedResp[i].isFolder)
|
||||
status = 'isFolder';
|
||||
|
||||
if (oldStatus !== status) {
|
||||
statusDiffers = true;
|
||||
rssFeedTable.updateRowData({
|
||||
rowId: i + 1,
|
||||
status: status
|
||||
});
|
||||
}
|
||||
}
|
||||
if (statusDiffers)
|
||||
rssFeedTable.updateIcons();
|
||||
|
||||
// get currently opened feed
|
||||
let openedFeedPath = undefined;
|
||||
if (rssFeedTable.selectedRows.length !== 0) {
|
||||
let lastSelectedRow = rssFeedTable.selectedRows[rssFeedTable.selectedRows.length - 1];
|
||||
openedFeedPath = rssFeedTable.rows[lastSelectedRow].full_data.dataPath;
|
||||
}
|
||||
|
||||
// check if list of articles differs
|
||||
let needsUpdate = false;
|
||||
flattenedResp.filter((r) => !r.isFolder)
|
||||
.each((r) => {
|
||||
let articlesDiffer = true;
|
||||
if (r.articles.length === feedData[r.uid].length) {
|
||||
articlesDiffer = false;
|
||||
for (let i = 0; i < r.articles.length; ++i) {
|
||||
if (feedData[r.uid][i].id !== r.articles[i].id)
|
||||
articlesDiffer = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (articlesDiffer) {
|
||||
// update unread count
|
||||
let oldUnread = feedData[r.uid].map((art) => !art.isRead).filter((v) => v).length;
|
||||
let newUnread = r.articles.map((art) => !art.isRead).filter((v) => v).length;
|
||||
let unreadDifference = newUnread - oldUnread;
|
||||
|
||||
// find all parents (and self) and add unread difference
|
||||
rssFeedTable.rows.filter((row) => r.fullName.slice(0, row.full_data.dataPath.length) === row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread += unreadDifference);
|
||||
needsUpdate = true;
|
||||
|
||||
// update data
|
||||
feedData[r.uid] = r.articles;
|
||||
|
||||
// if feed that is open changed, reload
|
||||
if (openedFeedPath !== undefined && r.fullName.slice(0, openedFeedPath.length) === openedFeedPath)
|
||||
showRssFeed(r.fullName);
|
||||
}
|
||||
else {
|
||||
// calculate read differnce and update feed data
|
||||
let readDifference = 0;
|
||||
let readChanged = false;
|
||||
for (let i = 0; i < r.articles.length; ++i) {
|
||||
let oldRead = feedData[r.uid][i].isRead ? 1 : 0;
|
||||
let newRead = r.articles[i].isRead ? 1 : 0;
|
||||
feedData[r.uid][i].isRead = r.articles[i].isRead;
|
||||
readDifference += oldRead - newRead;
|
||||
if (readDifference !== 0)
|
||||
readChanged = true;
|
||||
}
|
||||
|
||||
// if read on article changed
|
||||
if (readChanged) {
|
||||
needsUpdate = true;
|
||||
// find all items that contain this rss feed and add read difference
|
||||
rssFeedTable.rows.filter((row) => r.fullName.slice(0, row.full_data.dataPath.length) === row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread += readDifference);
|
||||
|
||||
// if feed that is opened changed update dynamicly
|
||||
if (openedFeedPath !== undefined && r.fullName.slice(0, openedFeedPath.length) === openedFeedPath) {
|
||||
for (let i = 0; i < r.articles.length; ++i) {
|
||||
let matchingRow = rssArticleTable.rows.filter((row) => row.full_data.feedUid === r.uid)
|
||||
.filter((row) => row.full_data.dataId === r.articles[i].id);
|
||||
matchingRow[Object.keys(matchingRow)[0]].full_data.isRead = r.articles[i].isRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (needsUpdate) {
|
||||
rssFeedTable.updateTable(true);
|
||||
rssArticleTable.updateTable(true);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// full refresh
|
||||
rssFeedTable.clear();
|
||||
feedData = {};
|
||||
pathByFeedId = new Map();
|
||||
|
||||
// Unread entry at top
|
||||
rssFeedTable.updateRowData({
|
||||
rowId: 0,
|
||||
name: 'Unread',
|
||||
unread: 0,
|
||||
status: 'unread',
|
||||
indentaion: 0,
|
||||
dataUid: '',
|
||||
dataUrl: '',
|
||||
dataPath: ''
|
||||
});
|
||||
|
||||
let rowCount = 1;
|
||||
for (let dataEntry of flattenedResp) {
|
||||
if (dataEntry.isFolder) {
|
||||
rssFeedTable.updateRowData({
|
||||
rowId: rowCount,
|
||||
name: dataEntry.name,
|
||||
unread: 0,
|
||||
status: 'isFolder',
|
||||
indentaion: dataEntry.depth,
|
||||
dataUid: '',
|
||||
dataUrl: '',
|
||||
dataPath: dataEntry.fullName
|
||||
});
|
||||
}
|
||||
else {
|
||||
let status = 'default';
|
||||
if (dataEntry.hasError)
|
||||
status = 'hasError';
|
||||
if (dataEntry.isLoading)
|
||||
status = 'isLoading';
|
||||
|
||||
rssFeedTable.updateRowData({
|
||||
rowId: rowCount,
|
||||
name: dataEntry.name,
|
||||
unread: 0,
|
||||
status: status,
|
||||
indentaion: dataEntry.depth,
|
||||
dataUid: dataEntry.uid,
|
||||
dataUrl: dataEntry.url,
|
||||
dataPath: dataEntry.fullName
|
||||
});
|
||||
|
||||
// calculate number of unread
|
||||
let numberOfUnread = dataEntry.articles.map((art) => !art.isRead).filter((v) => v).length;
|
||||
// find all items that contain this rss feed and add unread count
|
||||
rssFeedTable.rows.filter((row) => dataEntry.fullName.slice(0, row.full_data.dataPath.length) === row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread += numberOfUnread);
|
||||
|
||||
pathByFeedId.set(dataEntry.uid, dataEntry.fullName);
|
||||
feedData[dataEntry.uid] = dataEntry.articles;
|
||||
}
|
||||
++rowCount;
|
||||
}
|
||||
rssFeedTable.updateTable(false);
|
||||
rssFeedTable.updateIcons();
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
||||
const refreshFeed = (feedUid) => {
|
||||
// set icon to loading
|
||||
rssFeedTable.rows.forEach((row) => {
|
||||
if (row.full_data.dataUid === feedUid)
|
||||
row.full_data.status = 'isLoading';
|
||||
});
|
||||
rssFeedTable.updateIcons();
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/refreshItem',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
itemPath: pathByFeedId.get(feedUid)
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
||||
const refreshAllFeeds = () => {
|
||||
for (let feedEntry in feedData)
|
||||
refreshFeed(feedEntry);
|
||||
};
|
||||
|
||||
const moveItem = (oldPath) => {
|
||||
new MochaUI.Window({
|
||||
id: 'renamePage',
|
||||
title: 'QBT_TR(Please choose a new name for this RSS feed)QBT_TR[CONTEXT=RSSWidget]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'rename_feed.html?oldPath=' + encodeURIComponent(oldPath),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 100
|
||||
});
|
||||
};
|
||||
|
||||
const removeItem = (paths) => {
|
||||
new MochaUI.Window({
|
||||
id: 'confirmFeedDeletionPage',
|
||||
title: 'QBT_TR(Deletion confirmation)QBT_TR[CONTEXT=RSSWidget]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'confirmfeeddeletion.html?paths=' + encodeURIComponent(paths.join('|')),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 70
|
||||
});
|
||||
};
|
||||
|
||||
const markItemAsRead = (path) => {
|
||||
// feed data mark as read
|
||||
for (let feedID in feedData)
|
||||
if (pathByFeedId.get(feedID).slice(0, path.length) === path)
|
||||
feedData[feedID].each((el) => el.isRead = true);
|
||||
|
||||
// mark rows as read
|
||||
rssArticleTable.rows.each((el) => el.full_data.isRead = true);
|
||||
|
||||
// find all children and set unread count to 0
|
||||
rssFeedTable.rows.filter((row) => row.full_data.dataPath.slice(0, path.length) === path && path !== row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread = 0);
|
||||
|
||||
// find selected row
|
||||
let rowId, prevUnreadCount;
|
||||
rssFeedTable.rows.forEach((row) => {
|
||||
if (row.full_data.dataPath === path) {
|
||||
rowId = row.full_data.rowId;
|
||||
prevUnreadCount = row.full_data.unread;
|
||||
}
|
||||
});
|
||||
|
||||
// find all parents (and self) and subtract previous unread count
|
||||
rssFeedTable.rows.filter((row) => path.slice(0, row.full_data.dataPath.length) === row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread -= prevUnreadCount);
|
||||
|
||||
rssArticleTable.updateTable(false);
|
||||
rssFeedTable.updateTable(true);
|
||||
|
||||
// send request
|
||||
new Request({
|
||||
url: 'api/v2/rss/markAsRead',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
itemPath: path
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
||||
const markArticleAsRead = (path, id) => {
|
||||
// find row
|
||||
let rowId, name, uid, unread;
|
||||
rssFeedTable.rows.forEach((row) => {
|
||||
if (row.full_data.dataPath === path) {
|
||||
rowId = row.full_data.rowId;
|
||||
name = row.full_data.dataPath;
|
||||
uid = row.full_data.dataUid;
|
||||
unread = row.full_data.unread;
|
||||
}
|
||||
});
|
||||
// update feed data
|
||||
let prevReadState = true;
|
||||
feedData[uid].each((article) => {
|
||||
if (article.id === id) {
|
||||
prevReadState = article.isRead;
|
||||
article.isRead = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!prevReadState) {
|
||||
// find all items that contain this feed and subtract 1
|
||||
rssFeedTable.rows.filter((row) => path.slice(0, row.full_data.dataPath.length) === row.full_data.dataPath)
|
||||
.each((row) => row.full_data.unread -= 1);
|
||||
|
||||
rssFeedTable.updateTable(true);
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/markAsRead',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
itemPath: path,
|
||||
articleId: id
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
};
|
||||
|
||||
const markSelectedAsRead = () => {
|
||||
let selectedDatapaths = rssFeedTable.selectedRows
|
||||
.map((sRow) => rssFeedTable.rows[sRow].full_data.dataPath);
|
||||
// filter children
|
||||
let reducedDatapaths = selectedDatapaths.filter((path) =>
|
||||
selectedDatapaths.filter((innerPath) => path.slice(0, innerPath.length) === innerPath).length === 1
|
||||
);
|
||||
reducedDatapaths.each((path) => markItemAsRead(path));
|
||||
};
|
||||
|
||||
const openRssDownloader = () => {
|
||||
const id = 'rssdownloaderpage';
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: 'QBT_TR(Rss Downloader)QBT_TR[CONTEXT=AutomatedRssDownloader]',
|
||||
loadMethod: 'xhr',
|
||||
contentURL: 'views/rssDownloader.html',
|
||||
maximizable: false,
|
||||
width: loadWindowWidth(id, 800),
|
||||
height: loadWindowHeight(id, 650),
|
||||
onResize: () => {
|
||||
saveWindowSize(id);
|
||||
},
|
||||
resizeLimit: {
|
||||
'x' :[800,2500],
|
||||
'y' :[500,2000]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return exports();
|
||||
})();
|
||||
</script>
|
735
src/webui/www/private/views/rssDownloader.html
Normal file
735
src/webui/www/private/views/rssDownloader.html
Normal file
|
@ -0,0 +1,735 @@
|
|||
<style>
|
||||
#rssdownloaderpage_content {
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
#RssDownloader {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#leftRssDownloaderColumn {
|
||||
width: 25%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#rulesTable, #rssDownloaderArticlesTable {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#centerRssDownloaderColumn {
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#rightRssDownloaderColumn {
|
||||
width: 25%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.noWrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#rssDownloaderFeeds {
|
||||
height: calc(100% - 355px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#rssDownloaderFeedsTable {
|
||||
height: calc(100% - 21px);
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#saveButton {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.articleTableFeed td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.articleTableArticle td {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#rssDownloaderDisabled {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#rulesTable table, #rssDownloaderFeedsTable table, #rssDownloaderArticlesTable table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#ignoreDaysValue {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#lastMatchDiv {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="RssDownloader" class="RssDownloader">
|
||||
<div id="rssDownloaderDisabled" class="invisible">
|
||||
QBT_TR(Auto downloading of RSS torrents is disabled now! You can enable it in application settings.)QBT_TR[CONTEXT=AutomatedRssDownloader]
|
||||
</div>
|
||||
<div id="leftRssDownloaderColumn">
|
||||
<b id="rulesTableDesc">QBT_TR(Download Rules)QBT_TR[CONTEXT=AutomatedRssDownloader]</b>
|
||||
<div id="rulesTable">
|
||||
<div id="rulesSelectionCheckBoxList">
|
||||
<div id="rssDownloaderRuleFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div id="rssDownloaderRuleTableDiv" class="dynamicTableDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="centerRssDownloaderColumn">
|
||||
<fieldset class="settings" id="ruleSettings">
|
||||
<legend>QBT_TR(Rule Definition)QBT_TR[CONTEXT=AutomatedRssDownloader]</legend>
|
||||
<div class="formRow">
|
||||
<input disabled type="checkbox" id="useRegEx" onclick="qBittorrent.RssDownloader.setElementTitles()"/>
|
||||
<label for="useRegEx">QBT_TR(Use Regular Expressions)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</div>
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="mustContainText" class="noWrap">QBT_TR(Must Contain:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<input disabled type="text" id="mustContainText" class="fullWidth" autocorrect="off" autocapitalize="none" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="mustNotContainText" class="noWrap">QBT_TR(Must Not Contain:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<input disabled type="text" id="mustNotContainText" class="fullWidth" autocorrect="off" autocapitalize="none" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="episodeFilterText" class="noWrap">QBT_TR(Episode Filter:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<input disabled type="text" id="episodeFilterText" class="fullWidth" autocorrect="off" autocapitalize="none" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="formRow" title="QBT_TR(Smart Episode Filter will check the episode number to prevent downloading of duplicates.
|
||||
Supports the formats: S01E01, 1x1, 2017.01.01 and 01.01.2017 (Date formats also support - as a separator))QBT_TR[CONTEXT=AutomatedRssDownloader]">
|
||||
<input disabled type="checkbox" id="useSmartFilter" />
|
||||
<label for="useSmartFilter">QBT_TR(Use Smart Episode Filter)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="noWrap">QBT_TR(Assign Category:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<select disabled id="assignCategoryCombobox" class="fullWidth">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="formRow">
|
||||
<input disabled type="checkbox" id="savetoDifferentDir" />
|
||||
<label for="savetoDifferentDir">QBT_TR(Save to a Different Directory)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</div>
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="noWrap" for="saveToText">QBT_TR(Save to:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<input disabled type="text" class="fullWidth" id="saveToText" autocorrect="off" autocapitalize="none" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="ignoreDaysValue">QBT_TR(Ignore Subsequent Matches for (0 to Disable))QBT_TR[CONTEXT=AutomatedRssDownloader]</label></td>
|
||||
<td><input type="number" id="ignoreDaysValue" min="0" />QBT_TR( days)QBT_TR[CONTEXT=AutomatedRssDownloader]</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="lastMatchDiv">
|
||||
<span id="lastMatchText">QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]</span>
|
||||
</div>
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="noWrap">QBT_TR(Add Paused:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<select disabled id="addPausedCombobox" class="fullWidth">
|
||||
<option value="default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="always">QBT_TR(Always)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="never">QBT_TR(Never)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="noWrap">QBT_TR(Create Subfolder:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<select disabled id="creatSubfolderCombobox" class="fullWidth">
|
||||
<option value="default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="always">QBT_TR(Always)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="never">QBT_TR(Never)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="settings" id="rssDownloaderFeeds">
|
||||
<legend>QBT_TR(Apply Rule to Feeds:)QBT_TR[CONTEXT=AutomatedRssDownloader]</legend>
|
||||
<div id="rssDownloaderFeedsTable">
|
||||
<div id="rssDownloaderFeedSelectionFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div id="rssDownloaderFeedSelectionTableDiv" class="dynamicTableDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button disabled id="saveButton" onclick="qBittorrent.RssDownloader.saveSettings()">
|
||||
QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]
|
||||
</button>
|
||||
</div>
|
||||
<div id="rightRssDownloaderColumn">
|
||||
<b id="articleTableDesc">QBT_TR(Matching RSS Articles)QBT_TR[CONTEXT=AutomatedRssDownloader]</b>
|
||||
<div id="rssDownloaderArticlesTable">
|
||||
<div id="rssDownloaderArticlesFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div id="rssDownloaderArticlesTableDiv" class="dynamicTableDiv">
|
||||
<table class="dynamicTable unselectable">
|
||||
<thead>
|
||||
<tr class="dynamicTableHeader"></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="rssDownloaderRuleMenu" class="contextMenu">
|
||||
<li><a href="#addRule"><img src="icons/document-new.svg" alt="QBT_TR(Add new rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]" /> QBT_TR(Add new rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
|
||||
<li><a href="#deleteRule"><img src="icons/list-remove.svg" alt="QBT_TR(Delete rule)QBT_TR[CONTEXT=AutomatedRssDownloader]" /> QBT_TR(Delete rule)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
|
||||
<li class="separator"><a href="#renameRule"><img src="icons/edit-rename.svg" alt="QBT_TR(Rename rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]" /> QBT_TR(Rename rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
|
||||
<li class="separator"><a href="#clearDownloadedEpisodes"><img src="icons/edit-clear.svg" alt="QBT_TR(Clear downloaded episodes...)QBT_TR[CONTEXT=AutomatedRssDownloader]" /> QBT_TR(Clear downloaded episodes...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.RssDownloader = (() => {
|
||||
const exports = () => {
|
||||
return {
|
||||
updateRulesList: updateRulesList,
|
||||
showRule: showRule,
|
||||
renameRule: renameRule,
|
||||
modifyRuleState: modifyRuleState,
|
||||
saveSettings: saveSettings,
|
||||
rssDownloaderRulesTable: rssDownloaderRulesTable,
|
||||
rssDownloaderFeedSelectionTable: rssDownloaderFeedSelectionTable,
|
||||
setElementTitles: setElementTitles
|
||||
};
|
||||
};
|
||||
|
||||
let rssDownloaderRulesTable = new window.qBittorrent.DynamicTable.RssDownloaderRulesTable();
|
||||
let rssDownloaderFeedSelectionTable = new window.qBittorrent.DynamicTable.RssDownloaderFeedSelectionTable();
|
||||
let rssDownloaderArticlesTable = new window.qBittorrent.DynamicTable.RssDownloaderArticlesTable();
|
||||
|
||||
let rulesList = {};
|
||||
let feedList = [];
|
||||
|
||||
const initRssDownloader = () => {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/app/preferences',
|
||||
method: 'get',
|
||||
noCache: true,
|
||||
onFailure: () => {
|
||||
alert('Could not contact qBittorrent');
|
||||
},
|
||||
onSuccess: (pref) => {
|
||||
if (!pref.rss_auto_downloading_enabled)
|
||||
$('rssDownloaderDisabled').removeClass('invisible');
|
||||
|
||||
// recalculate height
|
||||
let warningHeight = $('rssDownloaderDisabled').getBoundingClientRect().height;
|
||||
|
||||
$('leftRssDownloaderColumn').style.height = 'calc(100% - ' + warningHeight + 'px)';
|
||||
$('centerRssDownloaderColumn').style.height = 'calc(100% - ' + warningHeight + 'px)';
|
||||
$('rightRssDownloaderColumn').style.height = 'calc(100% - ' + warningHeight + 'px)';
|
||||
|
||||
$('rulesTable').style.height = 'calc(100% - ' + $('rulesTableDesc').getBoundingClientRect().height + 'px)';
|
||||
$('rssDownloaderArticlesTable').style.height = 'calc(100% - ' + $('articleTableDesc').getBoundingClientRect().height + 'px)';
|
||||
|
||||
let centerRowNotTableHeight = $('saveButton').getBoundingClientRect().height +
|
||||
$('ruleSettings').getBoundingClientRect().height + 15;
|
||||
|
||||
$('rssDownloaderFeeds').style.height = 'calc(100% - ' + centerRowNotTableHeight + 'px)';
|
||||
|
||||
// firefox calculates the height of the table inside fieldset differently and thus doesn't need the offset
|
||||
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
|
||||
$('rssDownloaderFeedsTable').style.height = '100%';
|
||||
}
|
||||
else {
|
||||
let outsideTableHeight = ($('rssDownloaderFeedsTable').getBoundingClientRect().top - $('rssDownloaderFeeds').getBoundingClientRect().top) - 10;
|
||||
$('rssDownloaderFeedsTable').style.height = 'calc(100% - ' + outsideTableHeight + 'px)';
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
|
||||
const rssDownloaderRuleContextMenu = new window.qBittorrent.ContextMenu.RssDownloaderRuleContextMenu({
|
||||
targets: '',
|
||||
menu: 'rssDownloaderRuleMenu',
|
||||
actions: {
|
||||
addRule: addRule,
|
||||
deleteRule: (el) => {
|
||||
removeRule(rssDownloaderRulesTable.selectedRows
|
||||
.map((sRow) => rssDownloaderRulesTable.rows[sRow].full_data.name));
|
||||
},
|
||||
renameRule: (el) => {
|
||||
renameRule(rssDownloaderRulesTable.rows[rssDownloaderRulesTable.selectedRows[0]].full_data.name);
|
||||
},
|
||||
clearDownloadedEpisodes: (el) => {
|
||||
clearDownloadedEpisodes(rssDownloaderRulesTable.selectedRows
|
||||
.map((sRow) => rssDownloaderRulesTable.rows[sRow].full_data.name));
|
||||
}
|
||||
},
|
||||
offsets: {
|
||||
x: -22,
|
||||
y: -4
|
||||
}
|
||||
});
|
||||
rssDownloaderRulesTable.setup('rssDownloaderRuleTableDiv', 'rssDownloaderRuleFixedHeaderDiv', rssDownloaderRuleContextMenu);
|
||||
rssDownloaderFeedSelectionTable.setup('rssDownloaderFeedSelectionTableDiv', 'rssDownloaderFeedSelectionFixedHeaderDiv');
|
||||
rssDownloaderArticlesTable.setup('rssDownloaderArticlesTableDiv', 'rssDownloaderArticlesFixedHeaderDiv');
|
||||
|
||||
rssDownloaderRuleContextMenu.addTarget($('rulesTable'));
|
||||
// deselect feed when clicking on empty part of table
|
||||
$('rulesTable').addEventListener('click', (e) => {
|
||||
rssDownloaderRulesTable.deselectAll();
|
||||
rssDownloaderRulesTable.deselectRow();
|
||||
showRule('')
|
||||
});
|
||||
$('rulesTable').addEventListener('contextmenu', (e) => {
|
||||
if (e.toElement.nodeName === 'DIV') {
|
||||
rssDownloaderRulesTable.deselectAll();
|
||||
rssDownloaderRulesTable.deselectRow();
|
||||
rssDownloaderRuleContextMenu.updateMenuItems();
|
||||
showRule('')
|
||||
}
|
||||
});
|
||||
// get all categories and add to combobox
|
||||
new Request.JSON({
|
||||
url: 'api/v2/torrents/categories',
|
||||
noCache: true,
|
||||
method: 'get',
|
||||
onSuccess: (response) => {
|
||||
let combobox = $('assignCategoryCombobox');
|
||||
for (let cat in response) {
|
||||
let option = document.createElement('option');
|
||||
option.text = option.value = cat;
|
||||
combobox.add(option);
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
// get all rss feed
|
||||
new Request.JSON({
|
||||
url: 'api/v2/rss/items',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
withData: false
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
feedList = [];
|
||||
let flatten = (root) => {
|
||||
for (let child in root) {
|
||||
if (root[child].uid !== undefined)
|
||||
feedList.push({name: child, url: root[child].url});
|
||||
else
|
||||
flatten(root[child]);
|
||||
}
|
||||
}
|
||||
flatten(response);
|
||||
}
|
||||
}).send();
|
||||
$('savetoDifferentDir').addEvent('click', () => {
|
||||
$('saveToText').disabled = !$('savetoDifferentDir').checked;
|
||||
})
|
||||
updateRulesList();
|
||||
};
|
||||
|
||||
const updateRulesList = () => {
|
||||
// get all rules
|
||||
new Request.JSON({
|
||||
url: 'api/v2/rss/rules',
|
||||
noCache: true,
|
||||
method: 'get',
|
||||
onSuccess: (response) => {
|
||||
rssDownloaderRulesTable.clear();
|
||||
let rowCount = 0;
|
||||
for (let rule in response) {
|
||||
rssDownloaderRulesTable.updateRowData({
|
||||
rowId: rowCount++,
|
||||
checked: response[rule].enabled,
|
||||
name: rule
|
||||
});
|
||||
}
|
||||
rssDownloaderRulesTable.updateTable(false);
|
||||
rulesList = response;
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
const modifyRuleState = (rule, setting, newState, callback = () => {}) => {
|
||||
rulesList[rule][setting] = newState;
|
||||
new Request({
|
||||
url: 'api/v2/rss/setRule',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: rule,
|
||||
ruleDef: JSON.stringify(rulesList[rule])
|
||||
},
|
||||
onSuccess: () => {
|
||||
callback();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
const addRule = () => {
|
||||
new MochaUI.Window({
|
||||
id: 'newRulePage',
|
||||
title: 'QBT_TR(New rule name)QBT_TR[CONTEXT=AutomatedRssDownloader]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newrule.html',
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 100
|
||||
});
|
||||
};
|
||||
|
||||
const renameRule = (rule) => {
|
||||
new MochaUI.Window({
|
||||
id: 'renameRulePage',
|
||||
title: 'QBT_TR(Rule renaming)QBT_TR[CONTEXT=AutomatedRssDownloader]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'rename_rule.html?rule=' + encodeURIComponent(rule),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 100
|
||||
});
|
||||
};
|
||||
|
||||
const removeRule = (rules) => {
|
||||
new MochaUI.Window({
|
||||
id: 'removeRulePage',
|
||||
title: 'QBT_TR(Rule deletion confirmation)QBT_TR[CONTEXT=AutomatedRssDownloader]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'confirmruledeletion.html?rules=' + encodeURIComponent(rules.join('|')),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 360,
|
||||
height: 80
|
||||
});
|
||||
};
|
||||
|
||||
const clearDownloadedEpisodes = (rules) => {
|
||||
new MochaUI.Window({
|
||||
id: 'clearRulesPage',
|
||||
title: 'QBT_TR(New rule name)QBT_TR[CONTEXT=AutomatedRssDownloader]',
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'confirmruleclear.html?rules=' + encodeURIComponent(rules.join('|')),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
width: 350,
|
||||
height: 85
|
||||
});
|
||||
}
|
||||
|
||||
const saveSettings = () => {
|
||||
let lastSelectedRow = rssDownloaderRulesTable.selectedRows[rssDownloaderRulesTable.selectedRows.length - 1];
|
||||
let rule = rssDownloaderRulesTable.rows[lastSelectedRow].full_data.name;
|
||||
|
||||
rulesList[rule].useRegex = $('useRegEx').checked;
|
||||
rulesList[rule].mustContain = $('mustContainText').value;
|
||||
rulesList[rule].mustNotContain = $('mustNotContainText').value;
|
||||
rulesList[rule].episodeFilter = $('episodeFilterText').value;
|
||||
rulesList[rule].smartFilter = $('useSmartFilter').checked;
|
||||
rulesList[rule].assignedCategory = $('assignCategoryCombobox').value;
|
||||
rulesList[rule].savePath = $('savetoDifferentDir').checked ? $('saveToText').value : '';
|
||||
rulesList[rule].ignoreDays = parseInt($('ignoreDaysValue').value);
|
||||
|
||||
switch ($('addPausedCombobox').value) {
|
||||
case 'default':
|
||||
rulesList[rule].addPaused = null;
|
||||
break;
|
||||
case 'always':
|
||||
rulesList[rule].addPaused = true;
|
||||
break;
|
||||
case 'never':
|
||||
rulesList[rule].addPaused = false;
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($('creatSubfolderCombobox').value) {
|
||||
case 'default':
|
||||
rulesList[rule].createSubfolder = null;
|
||||
break;
|
||||
case 'always':
|
||||
rulesList[rule].createSubfolder = true;
|
||||
break;
|
||||
case 'never':
|
||||
rulesList[rule].createSubfolder = false;
|
||||
break;
|
||||
}
|
||||
|
||||
rulesList[rule].affectedFeeds = rssDownloaderFeedSelectionTable.rows.filter((row) => row.full_data.checked)
|
||||
.map((row) => row.full_data.url)
|
||||
.getValues();
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/setRule',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: rule,
|
||||
ruleDef: JSON.stringify(rulesList[rule])
|
||||
},
|
||||
onSuccess: () => {
|
||||
updateMatchingArticles(rule);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
const updateMatchingArticles = (ruleName) => {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/rss/matchingArticles',
|
||||
noCache: true,
|
||||
method: 'post',
|
||||
data: {
|
||||
ruleName: ruleName
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
rssDownloaderArticlesTable.clear();
|
||||
let rowCount = 0;
|
||||
for (let feed in response) {
|
||||
rssDownloaderArticlesTable.updateRowData({
|
||||
rowId: rowCount++,
|
||||
name: feed,
|
||||
isFeed: true
|
||||
});
|
||||
response[feed].each((article) => {
|
||||
rssDownloaderArticlesTable.updateRowData({
|
||||
rowId: rowCount++,
|
||||
name: article,
|
||||
isFeed: false
|
||||
});
|
||||
});
|
||||
}
|
||||
rssDownloaderArticlesTable.updateTable(false);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
const showRule = (ruleName) => {
|
||||
if (ruleName === '') {
|
||||
// disable all
|
||||
$('saveButton').disabled = true;
|
||||
$('useRegEx').disabled = true;
|
||||
$('mustContainText').disabled = true;
|
||||
$('mustNotContainText').disabled = true;
|
||||
$('episodeFilterText').disabled = true;
|
||||
$('useSmartFilter').disabled = true;
|
||||
$('assignCategoryCombobox').disabled = true;
|
||||
$('savetoDifferentDir').disabled = true;
|
||||
$('saveToText').disabled = true;
|
||||
$('ignoreDaysValue').disabled = true;
|
||||
$('addPausedCombobox').disabled = true;
|
||||
$('creatSubfolderCombobox').disabled = true;
|
||||
|
||||
// reset all boxes
|
||||
$('useRegEx').checked = false;
|
||||
$('mustContainText').value = '';
|
||||
$('mustNotContainText').value = '';
|
||||
$('episodeFilterText').value = '';
|
||||
$('useSmartFilter').checked = false;
|
||||
$('assignCategoryCombobox').value = 'default';
|
||||
$('savetoDifferentDir').checked = false;
|
||||
$('saveToText').value = '';
|
||||
$('ignoreDaysValue').value = 0;
|
||||
$('lastMatchText').innerHTML = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
$('addPausedCombobox').value = 'default';
|
||||
$('creatSubfolderCombobox').value = 'default';
|
||||
rssDownloaderFeedSelectionTable.clear();
|
||||
rssDownloaderArticlesTable.clear();
|
||||
|
||||
$('mustContainText').title = '';
|
||||
$('mustNotContainText').title = '';
|
||||
$('episodeFilterText').title = '';
|
||||
}
|
||||
else {
|
||||
// enable all
|
||||
$('saveButton').disabled = false;
|
||||
$('useRegEx').disabled = false;
|
||||
$('mustContainText').disabled = false;
|
||||
$('mustNotContainText').disabled = false;
|
||||
$('episodeFilterText').disabled = false;
|
||||
$('useSmartFilter').disabled = false;
|
||||
$('assignCategoryCombobox').disabled = false;
|
||||
$('savetoDifferentDir').disabled = false;
|
||||
$('savetoDifferentDir').checked = rulesList[ruleName].savePath ? false : true;
|
||||
$('saveToText').disabled = rulesList[ruleName].savePath ? false : true;
|
||||
$('ignoreDaysValue').disabled = false;
|
||||
$('addPausedCombobox').disabled = false;
|
||||
$('creatSubfolderCombobox').disabled = false;
|
||||
|
||||
// load rule settings
|
||||
$('useRegEx').checked = rulesList[ruleName].useRegex;
|
||||
$('mustContainText').value = rulesList[ruleName].mustContain;
|
||||
$('mustNotContainText').value = rulesList[ruleName].mustNotContain;
|
||||
$('episodeFilterText').value = rulesList[ruleName].episodeFilter;
|
||||
$('useSmartFilter').checked = rulesList[ruleName].smartFilter;
|
||||
|
||||
$('assignCategoryCombobox').value = rulesList[ruleName].assignedCategory ? rulesList[ruleName].assignedCategory : 'default';
|
||||
$('savetoDifferentDir').checked = rulesList[ruleName].savePath !== '';
|
||||
$('saveToText').value = rulesList[ruleName].savePath;
|
||||
$('ignoreDaysValue').value = rulesList[ruleName].ignoreDays;
|
||||
|
||||
// calculate days since last match
|
||||
if (rulesList[ruleName].lastMatch !== '') {
|
||||
let timeDiffInMs = new Date().getTime() - new Date(rulesList[ruleName].lastMatch).getTime();
|
||||
let daysAgo = Math.floor(timeDiffInMs / (1000 * 60 * 60 * 24)).toString();
|
||||
$('lastMatchText').innerHTML = ' QBT_TR(Last Match: %1 days ago)QBT_TR[CONTEXT=AutomatedRssDownloader]'.replace('%1', daysAgo);
|
||||
}
|
||||
else {
|
||||
$('lastMatchText').innerHTML = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
}
|
||||
|
||||
if (rulesList[ruleName].addPaused === null)
|
||||
$('addPausedCombobox').value = 'default';
|
||||
else
|
||||
$('addPausedCombobox').value = rulesList[ruleName].addPaused ? 'always' : 'never';
|
||||
|
||||
if (rulesList[ruleName].createSubfolder === null)
|
||||
$('creatSubfolderCombobox').value = 'default';
|
||||
else
|
||||
$('creatSubfolderCombobox').value = rulesList[ruleName].createSubfolder ? 'always' : 'never';
|
||||
|
||||
setElementTitles();
|
||||
|
||||
rssDownloaderFeedSelectionTable.clear();
|
||||
let rowCount = 0;
|
||||
feedList.forEach((feed) => {
|
||||
rssDownloaderFeedSelectionTable.updateRowData({
|
||||
rowId: rowCount++,
|
||||
checked: rulesList[ruleName].affectedFeeds.contains(feed.url),
|
||||
name: feed.name,
|
||||
url: feed.url
|
||||
});
|
||||
});
|
||||
rssDownloaderFeedSelectionTable.updateTable(false);
|
||||
updateMatchingArticles(ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
const setElementTitles = () => {
|
||||
let mainPart;
|
||||
if ($('useRegEx').checked) {
|
||||
mainPart = 'QBT_TR(Regex mode: use Perl-compatible regular expressions)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n';
|
||||
}
|
||||
else {
|
||||
mainPart = 'QBT_TR(Wildcard mode: you can use)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n' +
|
||||
' ● QBT_TR(? to match any single character)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(* to match zero or more of any characters)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Whitespaces count as AND operators (all words, any order))QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(| is used as OR operator)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n' +
|
||||
'QBT_TR(If word order is important use * instead of whitespace.)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n';
|
||||
}
|
||||
let secondPart = 'QBT_TR(An expression with an empty %1 clause (e.g. %2))QBT_TR[CONTEXT=AutomatedRssDownloader]'
|
||||
.replace('%1', '|').replace('%2', 'expr|');
|
||||
|
||||
$('mustContainText').title = mainPart + secondPart + 'QBT_TR( will match all articles.)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
$('mustNotContainText').title = mainPart + secondPart + 'QBT_TR( will exclude all articles.)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
|
||||
let episodeFilterTitle = 'QBT_TR(Matches articles based on episode filter.)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n' +
|
||||
'QBT_TR(Example: )QBT_TR[CONTEXT=AutomatedRssDownloader]' +
|
||||
'1x2;8-15;5;30-;' +
|
||||
'QBT_TR( will match 2, 5, 8 through 15, 30 and onward episodes of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n' +
|
||||
'QBT_TR(Episode filter rules: )QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n' +
|
||||
' ● QBT_TR(Season number is a mandatory non-zero value)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Episode number is a mandatory positive value)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Filter must end with semicolon)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Three range types for episodes are supported: )QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Single number: <b>1x25;</b> matches episode 25 of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n' +
|
||||
' ● QBT_TR(Infinite range: <b>1x25-;</b> matches episodes 25 and upward of season one, and all episodes of later seasons)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
|
||||
episodeFilterTitle = episodeFilterTitle.replace(/<b>/g, '').replace(/<\/b>/g, '');
|
||||
$('episodeFilterText').title = episodeFilterTitle;
|
||||
}
|
||||
|
||||
|
||||
initRssDownloader();
|
||||
return exports();
|
||||
})();
|
||||
</script>
|
|
@ -3,6 +3,9 @@
|
|||
<file>private/addpeers.html</file>
|
||||
<file>private/addtrackers.html</file>
|
||||
<file>private/confirmdeletion.html</file>
|
||||
<file>private/confirmfeeddeletion.html</file>
|
||||
<file>private/confirmruleclear.html</file>
|
||||
<file>private/confirmruledeletion.html</file>
|
||||
<file>private/css/Core.css</file>
|
||||
<file>private/css/dynamicTable.css</file>
|
||||
<file>private/css/Layout.css</file>
|
||||
|
@ -33,9 +36,14 @@
|
|||
<file>private/images/toolbox-divider.gif</file>
|
||||
<file>private/index.html</file>
|
||||
<file>private/newcategory.html</file>
|
||||
<file>private/newfeed.html</file>
|
||||
<file>private/newfolder.html</file>
|
||||
<file>private/newrule.html</file>
|
||||
<file>private/newtag.html</file>
|
||||
<file>private/rename.html</file>
|
||||
<file>private/rename_feed.html</file>
|
||||
<file>private/rename_file.html</file>
|
||||
<file>private/rename_rule.html</file>
|
||||
<file>private/scripts/client.js</file>
|
||||
<file>private/scripts/contextmenu.js</file>
|
||||
<file>private/scripts/download.js</file>
|
||||
|
@ -68,6 +76,8 @@
|
|||
<file>private/views/preferencesToolbar.html</file>
|
||||
<file>private/views/properties.html</file>
|
||||
<file>private/views/propertiesToolbar.html</file>
|
||||
<file>private/views/rss.html</file>
|
||||
<file>private/views/rssDownloader.html</file>
|
||||
<file>private/views/search.html</file>
|
||||
<file>private/views/searchplugins.html</file>
|
||||
<file>private/views/statistics.html</file>
|
||||
|
|
Loading…
Reference in a new issue