Sepro
5 years ago
18 changed files with 2902 additions and 27 deletions
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
Loading…
Reference in new issue