/* * This file is part of nzbget. See . * * Copyright (C) 2013-2019 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * In this module: * 1) Feeds menu; * 2) Feed view/preview dialog; * 3) Feed filter dialog. */ /*** FEEDS **********************************************/ var Feeds = (new function($) { 'use strict'; this.init = function() { } this.redraw = function() { var menu = $('#RssMenu'); var menuItemTemplate = $('.feed-menu-template', menu); menuItemTemplate.removeClass('feed-menu-template').removeClass('hide').addClass('feed-menu'); var insertPos = $('#RssMenu_Divider', menu); $('.feed-menu', menu).remove(); for (var i=1; ;i++) { var url = Options.option('Feed' + i + '.URL'); if (url === null) { break; } if (url.trim() !== '') { var item = menuItemTemplate.clone(); var name = Options.option('Feed' + i + '.Name'); var a = $('span', item); a.text(name !== '' ? name : 'Feed' + i); a.attr('data-id', i); a.click(viewFeed); var im = $('button', item); im.click(fetchFeed); im.attr('data-id', i); insertPos.before(item); } } Util.show('#RssMenuBlock', $('.feed-menu', menu).length > 0); } function viewFeed() { var id = parseInt($(this).attr('data-id')); FeedDialog.showModal(id); } function fetchFeed() { var id = parseInt($(this).attr('data-id')); RPC.call('fetchfeed', [id], function() { PopupNotification.show('#Notif_Feeds_Fetch'); }); } this.fetchAll = function() { RPC.call('fetchfeed', [0], function() { PopupNotification.show('#Notif_Feeds_Fetch'); }); } }(jQuery)); /*** FEEDS VIEW / PREVIEW DIALOG **********************************************/ var FeedDialog = (new function($) { 'use strict'; // Controls var $FeedDialog; var $ItemTable; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; this.init = function() { $FeedDialog = $('#FeedDialog'); $ItemTable = $('#FeedDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '#FeedDialog_ItemTable_filter', pagerContainer: '#FeedDialog_ItemTable_pager', rowSelect: UISettings.rowSelect, pageSize: pageSize, renderCellCallback: itemsTableRenderCellCallback }); $FeedDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); // resume updates Refresher.resume(); }); TabDialog.extend($FeedDialog); if (UISettings.setFocus) { $FeedDialog.on('shown', function() { //$('#FeedDialog_Name').focus(); }); } } this.showModal = function(id, name, url, filter, backlog, pauseNzb, category, priority, interval, feedscript) { Refresher.pause(); enableAllButtons(); $FeedDialog.restoreTab(); $('#FeedDialog_ItemTable_filter').val(''); $('#FeedDialog_ItemTable_pagerBlock').hide(); $ItemTable.fasttable('update', []); $ItemTable.fasttable('applyFilter', ''); items = null; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedDialog.modal({backdrop: 'static'}); $FeedDialog.maximize({mini: UISettings.miniTheme}); $('.loading-block', $FeedDialog).show(); if (name === undefined) { var name = Options.option('Feed' + id + '.Name'); $('#FeedDialog_Title').text(name !== '' ? name : 'Feed'); RPC.call('viewfeed', [id, false], itemsLoaded, feedFailure); } else { $('#FeedDialog_Title').text(name !== '' ? name : 'Feed Preview'); var feedBacklog = backlog === 'yes'; var feedPauseNzb = pauseNzb === 'yes'; var feedCategory = category; var feedPriority = parseInt(priority); var feedInterval = parseInt(interval); var feedScript = feedscript; RPC.call('previewfeed', [id, name, url, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, false, 0, ''], itemsLoaded, feedFailure); } if (!UISettings.miniTheme) { $('#FeedDialog_TableBlock').removeClass('modal-inner-scroll'); $('#FeedDialog_TableBlock').css('top', ''); $('#FeedDialog_TableBlock').css('top', $('#FeedDialog_TableBlock').position().top); $('#FeedDialog_TableBlock').addClass('modal-inner-scroll'); } } function feedFailure(res) { $FeedDialog.modal('hide'); AlertDialog.showModal('Error', res); } function disableAllButtons() { $('#FeedDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#FeedDialog_Transmit').show(); }, 500); } function enableAllButtons() { $('#FeedDialog .modal-footer .btn').removeAttr('disabled'); $('#FeedDialog_Transmit').hide(); } function itemsLoaded(itemsArr) { $('.loading-block', $FeedDialog).hide(); items = itemsArr; updateTable(); $('.modal-inner-scroll', $FeedDialog).scrollTop(100).scrollTop(0); } function updateTable() { var countNew = 0; var countFetched = 0; var countBacklog = 0; var differentFilenames = false; var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Status) { case 'UNKNOWN': status = 'UNKNOWN'; break; case 'BACKLOG': status = 'BACKLOG'; countBacklog +=1; break; case 'FETCHED': status = 'FETCHED'; countFetched +=1; break; case 'NEW': status = 'NEW'; countNew +=1; break; default: status = 'internal error(' + item.Status + ')'; } if (!(curFilter === item.Status || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var fields; if (!UISettings.miniTheme) { fields = ['
', status, name, item.Category, age, size]; } else { var info = '
' + name + '' + ' ' + status; if (item.Category !== '') { info += ' ' + item.Category + ''; } info += ' ' + age + '' + ' ' + size + ''; fields = [info]; } var item = { id: item.URL, item: item, fields: fields, data: { status: item.Status, name: itemName, category: item.Category, age: age, size: size, _search: true } }; data.push(item); } $ItemTable.fasttable('update', data); $ItemTable.fasttable('setCurPage', 1); Util.show('#FeedDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames) { if (countNew != undefined) { $('#FeedDialog_Badge_ALL,#FeedDialog_Badge_ALL2').text(countNew + countFetched + countBacklog); $('#FeedDialog_Badge_NEW,#FeedDialog_Badge_NEW2').text(countNew); $('#FeedDialog_Badge_FETCHED,#FeedDialog_Badge_FETCHED2').text(countFetched); $('#FeedDialog_Badge_BACKLOG,#FeedDialog_Badge_BACKLOG2').text(countBacklog); } $('#FeedDialog_Toolbar .btn').removeClass('btn-inverse'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedDialog .FeedDialog-names', differentFilenames); tableInitialized = true; } $('#FeedDialog_Titles,#FeedDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedDialog_Filenames,#FeedDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.fetch = function() { var checkedRows = $ItemTable.fasttable('checkedRows'); var checkedCount = $ItemTable.fasttable('checkedCount'); if (checkedCount === 0) { PopupNotification.show('#Notif_FeedDialog_Select'); return; } disableAllButtons(); var fetchItems = []; for (var i = 0; i < items.length; i++) { var item = items[i]; if (checkedRows[item.URL]) { fetchItems.push(item); } } fetchNextItem(fetchItems); } function fetchNextItem(fetchItems) { if (fetchItems.length > 0) { var name = fetchItems[0].Filename; if (name.substr(name.length-4, 4).toLowerCase() !== '.nzb') { name += '.nzb'; } RPC.call('append', [name, fetchItems[0].URL, fetchItems[0].AddCategory, fetchItems[0].Priority, false, false, fetchItems[0].DupeKey, fetchItems[0].DupeScore, fetchItems[0].DupeMode], function() { fetchItems.shift(); fetchNextItem(fetchItems); }) } else { $FeedDialog.modal('hide'); PopupNotification.show('#Notif_FeedDialog_Fetched'); } } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } }(jQuery)); /*** FEED FILTER DIALOG **********************************************/ var FeedFilterDialog = (new function($) { 'use strict'; // Controls var $FeedFilterDialog; var $ItemTable; var $Splitter; var $FilterInput; var $FilterBlock; var $FilterNumbers; var $PreviewBlock; var $ModalBody; var $LoadingBlock; var $CHAutoRematch; var $RematchIcon; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; var saveCallback; var splitStartPos; var feedId; var feedName; var feedUrl; var feedFilter; var feedBacklog; var feedPauseNzb; var feedCategory; var feedPriority; var feedInterval; var feedScript; var cacheTimeSec; var cacheId; var updating; var updateTimerIntitialized = false; var autoUpdate = false; var splitRatio; var firstUpdate; var lineNo; var showLines; this.init = function() { $FeedFilterDialog = $('#FeedFilterDialog'); $Splitter = $('#FeedFilterDialog_Splitter'); $Splitter.mousedown(splitterMouseDown); $('#FeedFilterDialog_Save').click(save); $FilterInput = $('#FeedFilterDialog_FilterInput'); $FilterBlock = $('#FeedFilterDialog_FilterBlock'); $FilterNumbers = $('#FeedFilterDialog_FilterNumbers'); $PreviewBlock = $('#FeedFilterDialog_PreviewBlock'); $ModalBody = $('.modal-body', $FeedFilterDialog); $LoadingBlock = $('.loading-block', $FeedFilterDialog); $CHAutoRematch = $('#FeedFilterDialog_CHAutoRematch'); $RematchIcon = $('#FeedFilterDialog_RematchIcon'); autoUpdate = UISettings.read('$FeedFilterDialog_AutoRematch', '1') == '1'; updateRematchState(); initLines(); $ItemTable = $('#FeedFilterDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '', pagerContainer: '#FeedFilterDialog_ItemTable_pager', headerCheck: '', pageSize: pageSize, renderCellCallback: itemsTableRenderCellCallback }); $ItemTable.on('mousedown', Util.disableShiftMouseDown); $FilterInput.keypress(filterKeyPress); $FeedFilterDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); $(window).off('resize', windowResized); // resume updates Refresher.resume(); }); TabDialog.extend($FeedFilterDialog); if (UISettings.setFocus) { $FeedFilterDialog.on('shown', function() { $FilterInput.focus(); }); } } this.showModal = function(id, name, url, filter, backlog, pauseNzb, category, priority, interval, feedscript, _saveCallback) { saveCallback = _saveCallback; Refresher.pause(); $ItemTable.fasttable('update', []); $FeedFilterDialog.restoreTab(); $(window).on('resize', windowResized); splitterRestore(); $('#FeedFilterDialog_ItemTable_pagerBlock').hide(); $FilterInput.val(filter.replace(/\s*%\s*/g, '\n')); items = null; firstUpdate = true; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedFilterDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedFilterDialog.modal({backdrop: 'static'}); $FeedFilterDialog.maximize({mini: UISettings.miniTheme}); updateLines(); $LoadingBlock.show(); $('#FeedFilterDialog_Title').text(name !== '' ? name : 'Feed Preview'); feedId = id; feedName = name; feedUrl = url; feedFilter = filter; feedBacklog = backlog === 'yes'; feedPauseNzb = pauseNzb === 'yes'; feedCategory = category; feedPriority = parseInt(priority); feedInterval = parseInt(interval); feedScript = feedscript; cacheId = '' + Math.random()*10000000; cacheTimeSec = 60*10; // 10 minutes if (url !== '') { RPC.call('previewfeed', [feedId, name, url, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); } else { $LoadingBlock.hide(); } } this.rematch = function() { updateFilter(); } function updateFilter() { if (feedUrl == '') { return; } tableInitialized = false; updating = true; var filter = $FilterInput.val().replace(/\n/g, '%'); RPC.call('previewfeed', [feedId, feedName, feedUrl, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); setTimeout(function() { if (updating) { $LoadingBlock.show(); } }, 500); } function feedFailure(msg, result) { updating = false; var filter = $FilterInput.val().replace(/\n/g, ' % '); if (firstUpdate && filter === feedFilter) { $FeedFilterDialog.modal('hide'); } $LoadingBlock.hide(); AlertDialog.showModal('Error', result ? result.error.message : msg); } function itemsLoaded(itemsArr) { updating = false; $LoadingBlock.hide(); items = itemsArr; updateTable(); if (firstUpdate) { $('.modal-inner-scroll', $FeedFilterDialog).scrollTop(100).scrollTop(0); } firstUpdate = false; if (!updateTimerIntitialized) { setupUpdateTimer(); updateTimerIntitialized = true; } } function updateTable() { var countAccepted = 0; var countRejected = 0; var countIgnored = 0; var differentFilenames = false; var filter = $FilterInput.val().split('\n'); var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Match) { case 'ACCEPTED': var addInfo = [item.AddCategory !== feedCategory ? 'category: ' + item.AddCategory : null, item.Priority !== feedPriority ? DownloadsUI.buildPriorityText(item.Priority) : null, item.PauseNzb !== feedPauseNzb ? (item.PauseNzb ? 'paused' : 'unpaused') : null, item.DupeScore != 0 ? 'dupe-score: ' + item.DupeScore : null, item.DupeKey !== '' ? 'dupe-key: ' + item.DupeKey : null, item.DupeMode !== 'SCORE' ? 'dupe-mode: ' + item.DupeMode.toLowerCase() : null]. filter(function(e){return e}).join('; '); status = 'ACCEPTED'; countAccepted += 1; break; case 'REJECTED': status = 'REJECTED'; countRejected += 1; break; case 'IGNORED': status = 'IGNORED'; countIgnored += 1; break; default: status = 'internal error(' + item.Match + ')'; break; } if (!(curFilter === item.Match || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var rule = ''; if (item.Rule > 0) { rule = ' ' + item.Rule + ' '; } var fields; if (!UISettings.miniTheme) { fields = [status, rule, name, item.Category, age, size]; } else { var info = '' + name + '' + ' ' + status; fields = [info]; } var dataItem = { id: item.URL, item: item, fields: fields, data: { match: item.Match, rule: item.Rule, title: itemName, category: item.Category, age: age, size: size, _search: true } }; data.push(dataItem); } $ItemTable.fasttable('update', data); Util.show('#FeedFilterDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames) { if (countAccepted != undefined) { $('#FeedFilterDialog_Badge_ALL,#FeedFilterDialog_Badge_ALL2').text(countAccepted + countRejected + countIgnored); $('#FeedFilterDialog_Badge_ACCEPTED,#FeedFilterDialog_Badge_ACCEPTED2').text(countAccepted); $('#FeedFilterDialog_Badge_REJECTED,#FeedFilterDialog_Badge_REJECTED2').text(countRejected); $('#FeedFilterDialog_Badge_IGNORED,#FeedFilterDialog_Badge_IGNORED2').text(countIgnored); } $('#FeedFilterDialog_Toolbar .FeedFilterDialog-filter .btn').removeClass('btn-inverse'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedFilterDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedFilterDialog .FeedFilterDialog-names', differentFilenames); tableInitialized = true; } $('#FeedFilterDialog_Titles,#FeedFilterDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedFilterDialog_Filenames,#FeedFilterDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedFilterDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } function save(e) { e.preventDefault(); $FeedFilterDialog.modal('hide'); var filter = $FilterInput.val().replace(/\n/g, ' % '); saveCallback(filter); } function setupUpdateTimer() { // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter if the query has not changed. var timer; var lastFilter = $FilterInput.val(); $FilterInput.keyup(function() { var timerCallback = function() { var value = $FilterInput.val(); if (value != lastFilter) { lastFilter = value; if (autoUpdate) { updateFilter(); } } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, 500); return false; }); } this.autoRematch = function() { autoUpdate = !autoUpdate; UISettings.write('$FeedFilterDialog_AutoRematch', autoUpdate ? '1' : '0'); updateRematchState(); if (autoUpdate) { updateFilter(); } } function updateRematchState() { Util.show($CHAutoRematch, autoUpdate); $RematchIcon.toggleClass('icon-process', !autoUpdate); $RematchIcon.toggleClass('icon-process-auto', autoUpdate); } function filterKeyPress(event) { if (event.which == 37) { event.preventDefault(); alert('Percent character (%) cannot be part of a filter because it is used\nas line separator when saving filter into configuration file.'); } } /*** SPLITTER ***/ function splitterMouseDown(e) { e.stopPropagation(); e.preventDefault(); splitStartPos = e.pageX; $(document).bind("mousemove", splitterMouseMove).bind("mouseup", splitterMouseUp); $ModalBody.css('cursor', 'col-resize'); $FilterInput.css('cursor', 'col-resize'); } function splitterMouseMove(e) { var newPos = e.pageX; var right = $PreviewBlock.position().left + $PreviewBlock.width(); newPos = newPos < 150 ? 150 : newPos; newPos = newPos > right - 150 ? right - 150 : newPos; splitterMove(newPos - splitStartPos); splitStartPos = newPos; } function splitterMouseUp(e) { $ModalBody.css('cursor', ''); $FilterInput.css('cursor', ''); $(document).unbind("mousemove", splitterMouseMove).unbind("mouseup", splitterMouseUp); splitterSave(); } function splitterMove(delta) { $FilterBlock.css('width', parseInt($FilterBlock.css('width')) + delta); $PreviewBlock.css('left', parseInt($PreviewBlock.css('left')) + delta); $Splitter.css('left', parseInt($Splitter.css('left')) + delta); } function splitterSave() { if (!UISettings.miniTheme) { splitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); UISettings.write('$FeedFilterDialog_SplitRatio', splitRatio); } } function splitterRestore() { if (!UISettings.miniTheme) { var oldSplitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); splitRatio = UISettings.read('$FeedFilterDialog_SplitRatio', oldSplitRatio); windowResized(); } } function windowResized() { if (!UISettings.miniTheme) { var oldWidth = parseInt($FilterBlock.css('width')); var winWidth = $(window).width(); var newWidth = Math.round(winWidth * splitRatio); var right = winWidth - 30; newWidth = newWidth > right - 150 ? right - 150 : newWidth; newWidth = newWidth < 150 ? 150 : newWidth; splitterMove(newWidth - oldWidth); } } /*** LINE SELECTION ***/ this.selectRule = function(rule) { selectTextareaLine($FilterInput[0], rule); } function selectTextareaLine(tarea, lineNum) { lineNum--; // array starts at 0 var lines = tarea.value.split("\n"); // calculate start/end var startPos = 0; for (var x = 0; x < lines.length; x++) { if (x == lineNum) { break; } startPos += (lines[x].length+1); } var endPos = lines[lineNum].length+startPos; if (typeof(tarea.selectionStart) != "undefined") { tarea.focus(); tarea.selectionStart = startPos; tarea.selectionEnd = endPos; } } /*** LINE NUMBERS ***/ // Idea and portions of code from LinedTextArea plugin by Alan Williamson // http://files.aw20.net/jquery-linedtextarea/jquery-linedtextarea.html function initLines() { showLines = !UISettings.miniTheme; if (showLines) { lineNo = 1; $FilterInput.scroll(updateLines); } } function updateLines() { if (!UISettings.miniTheme && showLines) { var domTextArea = $FilterInput[0]; var scrollTop = domTextArea.scrollTop; var clientHeight = domTextArea.clientHeight; $FilterNumbers.css('margin-top', (-1*scrollTop) + "px"); lineNo = fillOutLines(scrollTop + clientHeight, lineNo); } } function fillOutLines(h, lineNo) { while ($FilterNumbers.height() - h <= 0) { $FilterNumbers.append("
" + lineNo + "
"); lineNo++; } return lineNo; } }(jQuery));