mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 14:55:20 -07:00
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
7 changed files with 129 additions and 52 deletions
|
@ -46,7 +46,7 @@ namespace NzbDrone.Api.ClientSchema
|
||||||
field.Value = value;
|
field.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldAttribute.Type == FieldType.Select)
|
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.Tag)
|
||||||
{
|
{
|
||||||
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
|
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ namespace NzbDrone.Api.ClientSchema
|
||||||
|
|
||||||
private static List<SelectOption> GetSelectOptions(Type selectOptions)
|
private static List<SelectOption> GetSelectOptions(Type selectOptions)
|
||||||
{
|
{
|
||||||
if (selectOptions == typeof(Profile))
|
if (selectOptions == null || selectOptions == typeof(Profile))
|
||||||
{
|
{
|
||||||
return new List<SelectOption>();
|
return new List<SelectOption>();
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ namespace NzbDrone.Api.ClientSchema
|
||||||
var options = from Enum e in Enum.GetValues(selectOptions)
|
var options = from Enum e in Enum.GetValues(selectOptions)
|
||||||
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
|
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.Username = Settings.Username;
|
||||||
query.Passkey = Settings.ApiKey;
|
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
|
// Require Internal only if came from RSS sync
|
||||||
if (Settings.RequireInternal && query.ImdbInfo == null)
|
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.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.HDBits
|
namespace NzbDrone.Core.Indexers.HDBits
|
||||||
{
|
{
|
||||||
|
@ -21,6 +26,10 @@ namespace NzbDrone.Core.Indexers.HDBits
|
||||||
public HDBitsSettings()
|
public HDBitsSettings()
|
||||||
{
|
{
|
||||||
BaseUrl = "https://hdbits.org";
|
BaseUrl = "https://hdbits.org";
|
||||||
|
|
||||||
|
Categories = new int[] { (int)HdBitsCategory.Movie };
|
||||||
|
Codecs = new int[0];
|
||||||
|
Mediums = new int[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Username")]
|
[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.")]
|
[FieldDefinition(4, Label = "Require Internal", Type = FieldType.Checkbox, HelpText = "Require Internal releases for release to be accepted.")]
|
||||||
public bool RequireInternal { get; set; }
|
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()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
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>
|
<label class="col-sm-3 control-label">{{label}}</label>
|
||||||
|
|
||||||
<div class="col-sm-5">
|
<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>
|
</div>
|
||||||
|
|
||||||
{{> FormHelpPartial}}
|
{{> FormHelpPartial}}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
var Handlebars = require('handlebars');
|
var Handlebars = require('handlebars');
|
||||||
|
|
||||||
Handlebars.registerHelper('TitleCase', function(input) {
|
Handlebars.registerHelper('TitleCase', function(input) {
|
||||||
return new Handlebars.SafeString(input.replace(/\w\S*/g, function(txt) {
|
return new Handlebars.SafeString(input.replace(/\w\S*/g, function(txt) {
|
||||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
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 _ = require('underscore');
|
||||||
var TagCollection = require('../Tags/TagCollection');
|
var TagCollection = require('../Tags/TagCollection');
|
||||||
var TagModel = require('../Tags/TagModel');
|
var TagModel = require('../Tags/TagModel');
|
||||||
require('bootstrap.tagsinput');
|
require('bootstrap.tagsinput');
|
||||||
|
|
||||||
var substringMatcher = function(tagCollection) {
|
var substringMatcher = function(tags, selector) {
|
||||||
return function findMatches (q, cb) {
|
return function findMatches (q, cb) {
|
||||||
q = q.replace(/[^-_a-z0-9]/gi, '').toLowerCase();
|
q = q.replace(/[^-_a-z0-9]/gi, '').toLowerCase();
|
||||||
var matches = _.select(tagCollection.toJSON(), function(tag) {
|
var matches = _.select(tags, function(tag) {
|
||||||
return tag.label.toLowerCase().indexOf(q) > -1;
|
return selector(tag).toLowerCase().indexOf(q) > -1;
|
||||||
});
|
});
|
||||||
cb(matches);
|
cb(matches);
|
||||||
};
|
};
|
||||||
|
@ -108,49 +108,91 @@ $.fn.tagsinput.Constructor.prototype.build = function(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$.fn.tagInput = function(options) {
|
$.fn.tagInput = function(options) {
|
||||||
options = $.extend({}, { allowNew : true }, options);
|
|
||||||
|
|
||||||
var input = this;
|
this.each(function () {
|
||||||
var model = options.model;
|
|
||||||
var property = options.property;
|
|
||||||
|
|
||||||
var tagInput = $(this).tagsinput({
|
var input = $(this);
|
||||||
tagCollection : TagCollection,
|
var tagInput = null;
|
||||||
freeInput : true,
|
|
||||||
allowNew : options.allowNew,
|
if (input[0].hasAttribute('tag-source')) {
|
||||||
itemValue : 'id',
|
|
||||||
itemText : 'label',
|
var listItems = JSON.parse(input.attr('tag-source'));
|
||||||
trimValue : true,
|
|
||||||
typeaheadjs : {
|
tagInput = input.tagsinput({
|
||||||
name : 'tags',
|
freeInput: false,
|
||||||
displayKey : 'label',
|
allowNew: false,
|
||||||
source : substringMatcher(TagCollection)
|
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 model = options.model;
|
||||||
|
var property = options.property;
|
||||||
|
|
||||||
|
tagInput = input.tagsinput({
|
||||||
|
tagCollection: TagCollection,
|
||||||
|
freeInput: true,
|
||||||
|
allowNew: options.allowNew,
|
||||||
|
itemValue: 'id',
|
||||||
|
itemText: 'label',
|
||||||
|
trimValue: true,
|
||||||
|
typeaheadjs: {
|
||||||
|
name: 'tags',
|
||||||
|
displayKey: 'label',
|
||||||
|
source: substringMatcher(TagCollection.toJSON(), function (t) { return t.label; })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Override the free input being set to false because we're using objects
|
||||||
|
$(tagInput)[0].options.freeInput = true;
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
var tags = getExistingTags(model.get(property));
|
||||||
|
|
||||||
|
//Remove any existing tags and re-add them
|
||||||
|
input.tagsinput('removeAll');
|
||||||
|
_.each(tags, function (tag) {
|
||||||
|
input.tagsinput('add', tag);
|
||||||
|
});
|
||||||
|
input.tagsinput('refresh');
|
||||||
|
input.on('itemAdded', function (event) {
|
||||||
|
var tags = model.get(property);
|
||||||
|
tags.push(event.item.id);
|
||||||
|
model.set(property, tags);
|
||||||
|
});
|
||||||
|
input.on('itemRemoved', function (event) {
|
||||||
|
if (!event.item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tags = _.without(model.get(property), event.item.id);
|
||||||
|
model.set(property, tags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Override the free input being set to false because we're using objects
|
|
||||||
$(tagInput)[0].options.freeInput = true;
|
|
||||||
|
|
||||||
if (model) {
|
|
||||||
var tags = getExistingTags(model.get(property));
|
|
||||||
|
|
||||||
//Remove any existing tags and re-add them
|
|
||||||
$(this).tagsinput('removeAll');
|
|
||||||
_.each(tags, function(tag) {
|
|
||||||
$(input).tagsinput('add', tag);
|
|
||||||
});
|
|
||||||
$(this).tagsinput('refresh');
|
|
||||||
$(this).on('itemAdded', function(event) {
|
|
||||||
var tags = model.get(property);
|
|
||||||
tags.push(event.item.id);
|
|
||||||
model.set(property, tags);
|
|
||||||
});
|
|
||||||
$(this).on('itemRemoved', function(event) {
|
|
||||||
if (!event.item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var tags = _.without(model.get(property), event.item.id);
|
|
||||||
model.set(property, tags);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var $ = require('jquery');
|
var $ = require('jquery');
|
||||||
var vent = require('vent');
|
var vent = require('vent');
|
||||||
var Marionette = require('marionette');
|
var Marionette = require('marionette');
|
||||||
|
@ -8,11 +8,16 @@ var AsValidatedView = require('../../../Mixins/AsValidatedView');
|
||||||
var AsEditModalView = require('../../../Mixins/AsEditModalView');
|
var AsEditModalView = require('../../../Mixins/AsEditModalView');
|
||||||
require('../../../Form/FormBuilder');
|
require('../../../Form/FormBuilder');
|
||||||
require('../../../Mixins/AutoComplete');
|
require('../../../Mixins/AutoComplete');
|
||||||
|
require('../../../Mixins/TagInput');
|
||||||
require('bootstrap');
|
require('bootstrap');
|
||||||
|
|
||||||
var view = Marionette.ItemView.extend({
|
var view = Marionette.ItemView.extend({
|
||||||
template : 'Settings/Indexers/Edit/IndexerEditViewTemplate',
|
template : 'Settings/Indexers/Edit/IndexerEditViewTemplate',
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
tags : '.x-form-tag'
|
||||||
|
},
|
||||||
|
|
||||||
events : {
|
events : {
|
||||||
'click .x-back' : '_back',
|
'click .x-back' : '_back',
|
||||||
'click .x-captcha-refresh' : '_onRefreshCaptcha'
|
'click .x-captcha-refresh' : '_onRefreshCaptcha'
|
||||||
|
@ -24,6 +29,10 @@ var view = Marionette.ItemView.extend({
|
||||||
this.targetCollection = options.targetCollection;
|
this.targetCollection = options.targetCollection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRender: function () {
|
||||||
|
this.ui.tags.tagInput({});
|
||||||
|
},
|
||||||
|
|
||||||
_onAfterSave : function() {
|
_onAfterSave : function() {
|
||||||
this.targetCollection.add(this.model, { merge : true });
|
this.targetCollection.add(this.model, { merge : true });
|
||||||
vent.trigger(vent.Commands.CloseModalCommand);
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue