mirror of https://github.com/Radarr/Radarr
Added HDBits Category, Codec, and Medium Filtering Capability (#1458)
* Added advanced configuration options to support filtering Categories, Codecs, and Medium to the HDBits indexer. * Changes to use the existing tags with a controlled vocabulary. * 1) Sorting select options by name 2) Moved the autocomplete tag code into taginput as requested * removed commented out line * require cleanups
This commit is contained in:
parent
86634006e5
commit
3d48da2111
|
@ -46,7 +46,7 @@ namespace NzbDrone.Api.ClientSchema
|
|||
field.Value = value;
|
||||
}
|
||||
|
||||
if (fieldAttribute.Type == FieldType.Select)
|
||||
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.Tag)
|
||||
{
|
||||
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ namespace NzbDrone.Api.ClientSchema
|
|||
|
||||
private static List<SelectOption> GetSelectOptions(Type selectOptions)
|
||||
{
|
||||
if (selectOptions == typeof(Profile))
|
||||
if (selectOptions == null || selectOptions == typeof(Profile))
|
||||
{
|
||||
return new List<SelectOption>();
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ namespace NzbDrone.Api.ClientSchema
|
|||
var options = from Enum e in Enum.GetValues(selectOptions)
|
||||
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
|
||||
|
||||
return options.OrderBy(o => o.Value).ToList();
|
||||
return options.OrderBy(o => o.Name).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||
query.Username = Settings.Username;
|
||||
query.Passkey = Settings.ApiKey;
|
||||
|
||||
query.Category = Settings.Categories.ToArray();
|
||||
query.Codec = Settings.Codecs.ToArray();
|
||||
query.Medium = Settings.Mediums.ToArray();
|
||||
|
||||
// Require Internal only if came from RSS sync
|
||||
if (Settings.RequireInternal && query.ImdbInfo == null)
|
||||
{
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
using FluentValidation;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using System.Linq.Expressions;
|
||||
using FluentValidation.Results;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.HDBits
|
||||
{
|
||||
|
@ -21,6 +26,10 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||
public HDBitsSettings()
|
||||
{
|
||||
BaseUrl = "https://hdbits.org";
|
||||
|
||||
Categories = new int[] { (int)HdBitsCategory.Movie };
|
||||
Codecs = new int[0];
|
||||
Mediums = new int[0];
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Username")]
|
||||
|
@ -38,6 +47,15 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||
[FieldDefinition(4, Label = "Require Internal", Type = FieldType.Checkbox, HelpText = "Require Internal releases for release to be accepted.")]
|
||||
public bool RequireInternal { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Categories", Type = FieldType.Tag, SelectOptions = typeof(HdBitsCategory), Advanced = true, HelpText = "Options: Movie, TV, Documentary, Music, Sport, Audio, XXX, MiscDemo. If unspecified, all options are used.")]
|
||||
public IEnumerable<int> Categories { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Codecs", Type = FieldType.Tag, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
|
||||
public IEnumerable<int> Codecs { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Mediums", Type = FieldType.Tag, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
|
||||
public IEnumerable<int> Mediums { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<div class="form-group {{#if advanced}}advanced-setting{{/if}}">
|
||||
<div class="form-group {{#if advanced}}advanced-setting{{/if}}">
|
||||
<label class="col-sm-3 control-label">{{label}}</label>
|
||||
|
||||
<div class="col-sm-5">
|
||||
<input type="text" name="fields.{{order}}.value" validation-name="{{name}}" class="form-control x-form-tag"/>
|
||||
<input type="text" name="fields.{{order}}.value" validation-name="{{name}}" tag-source="{{json selectOptions}}" class="form-control x-form-tag"/>
|
||||
</div>
|
||||
|
||||
{{> FormHelpPartial}}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
var Handlebars = require('handlebars');
|
||||
var Handlebars = require('handlebars');
|
||||
|
||||
Handlebars.registerHelper('TitleCase', function(input) {
|
||||
return new Handlebars.SafeString(input.replace(/\w\S*/g, function(txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
}));
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('json', function (obj) {
|
||||
return JSON.stringify(obj);
|
||||
});
|
|
@ -1,14 +1,14 @@
|
|||
var $ = require('jquery');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var TagCollection = require('../Tags/TagCollection');
|
||||
var TagModel = require('../Tags/TagModel');
|
||||
require('bootstrap.tagsinput');
|
||||
|
||||
var substringMatcher = function(tagCollection) {
|
||||
var substringMatcher = function(tags, selector) {
|
||||
return function findMatches (q, cb) {
|
||||
q = q.replace(/[^-_a-z0-9]/gi, '').toLowerCase();
|
||||
var matches = _.select(tagCollection.toJSON(), function(tag) {
|
||||
return tag.label.toLowerCase().indexOf(q) > -1;
|
||||
var matches = _.select(tags, function(tag) {
|
||||
return selector(tag).toLowerCase().indexOf(q) > -1;
|
||||
});
|
||||
cb(matches);
|
||||
};
|
||||
|
@ -108,13 +108,51 @@ $.fn.tagsinput.Constructor.prototype.build = function(options) {
|
|||
};
|
||||
|
||||
$.fn.tagInput = function(options) {
|
||||
|
||||
this.each(function () {
|
||||
|
||||
var input = $(this);
|
||||
var tagInput = null;
|
||||
|
||||
if (input[0].hasAttribute('tag-source')) {
|
||||
|
||||
var listItems = JSON.parse(input.attr('tag-source'));
|
||||
|
||||
tagInput = input.tagsinput({
|
||||
freeInput: false,
|
||||
allowNew: false,
|
||||
allowDuplicates: false,
|
||||
itemValue: 'value',
|
||||
itemText: 'name',
|
||||
typeaheadjs: {
|
||||
displayKey: 'name',
|
||||
source: substringMatcher(listItems, function (t) { return t.name; })
|
||||
}
|
||||
});
|
||||
|
||||
var origValue = input.val();
|
||||
|
||||
input.tagsinput('removeAll');
|
||||
|
||||
if (origValue) {
|
||||
_.each(origValue.split(','), function (v) {
|
||||
var parsed = parseInt(v);
|
||||
var found = _.find(listItems, function (t) { return t.value === parsed; });
|
||||
|
||||
if (found) {
|
||||
input.tagsinput('add', found);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
options = $.extend({}, { allowNew: true }, options);
|
||||
|
||||
var input = this;
|
||||
var model = options.model;
|
||||
var property = options.property;
|
||||
|
||||
var tagInput = $(this).tagsinput({
|
||||
tagInput = input.tagsinput({
|
||||
tagCollection: TagCollection,
|
||||
freeInput: true,
|
||||
allowNew: options.allowNew,
|
||||
|
@ -124,7 +162,7 @@ $.fn.tagInput = function(options) {
|
|||
typeaheadjs: {
|
||||
name: 'tags',
|
||||
displayKey: 'label',
|
||||
source : substringMatcher(TagCollection)
|
||||
source: substringMatcher(TagCollection.toJSON(), function (t) { return t.label; })
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -135,17 +173,17 @@ $.fn.tagInput = function(options) {
|
|||
var tags = getExistingTags(model.get(property));
|
||||
|
||||
//Remove any existing tags and re-add them
|
||||
$(this).tagsinput('removeAll');
|
||||
input.tagsinput('removeAll');
|
||||
_.each(tags, function (tag) {
|
||||
$(input).tagsinput('add', tag);
|
||||
input.tagsinput('add', tag);
|
||||
});
|
||||
$(this).tagsinput('refresh');
|
||||
$(this).on('itemAdded', function(event) {
|
||||
input.tagsinput('refresh');
|
||||
input.on('itemAdded', function (event) {
|
||||
var tags = model.get(property);
|
||||
tags.push(event.item.id);
|
||||
model.set(property, tags);
|
||||
});
|
||||
$(this).on('itemRemoved', function(event) {
|
||||
input.on('itemRemoved', function (event) {
|
||||
if (!event.item) {
|
||||
return;
|
||||
}
|
||||
|
@ -153,4 +191,8 @@ $.fn.tagInput = function(options) {
|
|||
model.set(property, tags);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
var _ = require('underscore');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
|
@ -8,11 +8,16 @@ var AsValidatedView = require('../../../Mixins/AsValidatedView');
|
|||
var AsEditModalView = require('../../../Mixins/AsEditModalView');
|
||||
require('../../../Form/FormBuilder');
|
||||
require('../../../Mixins/AutoComplete');
|
||||
require('../../../Mixins/TagInput');
|
||||
require('bootstrap');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template : 'Settings/Indexers/Edit/IndexerEditViewTemplate',
|
||||
|
||||
ui: {
|
||||
tags : '.x-form-tag'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-back' : '_back',
|
||||
'click .x-captcha-refresh' : '_onRefreshCaptcha'
|
||||
|
@ -24,6 +29,10 @@ var view = Marionette.ItemView.extend({
|
|||
this.targetCollection = options.targetCollection;
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.ui.tags.tagInput({});
|
||||
},
|
||||
|
||||
_onAfterSave : function() {
|
||||
this.targetCollection.add(this.model, { merge : true });
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
|
|
Loading…
Reference in New Issue