Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[{
"className": "row",
"fieldGroup": [{
"key": "name",
"type": "input",
"templateOptions": {
"required": true,
"label": "Search Engine Name",
"type": "text"
},
"className": "col-xs-9",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.name }} </p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "mirror",
"type": "input",
"templateOptions": {
"required": true,
"label": "Base URL for site (exclude the final /)",
"type": "text"
},
"className": "col-xs-9",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.mirror }} </p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "searchEndpoint",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Search page url (use %s to inject search query)",
"type": "text"
},
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "testSearch",
"className": "cseProperty col-xs-3",
"type": "input",
"templateOptions": {
"required": true,
"label": "Test SearchQuery",
"type": "text"
}
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.searchEndpoint }} </p><p>{{ model.infoMessages.testSearch}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "searchResultsContainer",
"type": "input",
"templateOptions": {
"required": true,
"label": "Results selector (CSS selector that returns a base element for each individual search result)",
"type": "text"
},
"asyncValidators": "$mappings.validators.searchResultsContainer",
"className": "col-xs-9",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.searchResultsContainer }} </p><p>{{ model.infoMessages.testSearch}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "loginRequired",
"className": "col-xs-5 inline-checkbox",
"type": "input",
"templateOptions": {
"label": "Login Required?",
"type": "checkbox"
}
}, {
"key": "magnetSupported",
"className": "col-xs-5 inline-checkbox",
"type": "input",
"templateOptions": {
"label": "Magnet URI supported?",
"type": "checkbox"
},
"asyncValidators": "$mappings.validators.propertySelector"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "releaseNameSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Release name Selector (within base element)",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "releaseNameProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"required": true,
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
}
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.releaseNameSelector }} </p><p>{{ model.infoMessages.releaseNameProperty}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "loginPage",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"label": "URL of the login page (for when not logged in)",
"placeholder": "/login.php",
"type": "text"
},
"asyncValidators": "$mappings.validators.loginPage",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "loginTestSelector",
"className": "cseProperty col-xs-3",
"type": "input",
"templateOptions": {
"requiredExpression": "model.loginRequired",
"label": "Selector that tests if we're not loggedin when executing a search",
"placeholder": "#loginform",
"type": "text"
},
"asyncValidators": "$mappings.validators.loginTestSelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.loginPage }} </p><p>{{ model.infoMessages.loginTestSelector}}</p></div>"
}],
"hideExpression": "!model.loginRequired"
}, {
"className": "row",
"fieldGroup": [{
"key": "magnetUrlSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"label": "magnet URL selector (hyperlink to the magnet url)",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "magnetUrlProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.propertySelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.magnetUrlSelector }} </p><p>{{ model.infoMessages.magnetUrlProperty}}</p></div>"
}],
"hideExpression": "!model.magnetSupported"
}, {
"className": "row",
"fieldGroup": [{
"key": "torrentUrlSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"label": ".torrent URL selector (hyperlink to the torrent file)",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "torrentUrlProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.propertySelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.torrentUrlSelector }} </p><p>{{ model.infoMessages.torrentUrlProperty}}</p></div>"
}],
"hideExpression": "!model.magnetSupported"
}, {
"className": "row",
"fieldGroup": [{
"key": "sizeSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Size Selector (element that has the Torrent's size)",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "sizeProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"required": true,
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.attributeSelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.sizeSelector }} </p><p>{{ model.infoMessages.sizeProperty}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "seederSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Seeders Selector (element that has the 'seeders')",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "seederProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"required": true,
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.attributeSelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.seederSelector }} </p><p>{{ model.infoMessages.seederProperty}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "leecherSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Leechers Selector (element that has the 'leechers')",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "leecherProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"required": true,
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.attributeSelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.leecherSelector }} </p><p>{{ model.infoMessages.leecherProperty}}</p></div>"
}]
}, {
"className": "row",
"fieldGroup": [{
"key": "detailUrlSelector",
"className": "cseSelector col-xs-6",
"type": "input",
"templateOptions": {
"required": true,
"label": "Detail URL Selector (page that opens in new tab and shows detail page for torrent)",
"type": "text"
},
"asyncValidators": "$mappings.validators.propertySelector",
"modelOptions": "$mappings.modelOptions.keyup"
}, {
"key": "detailUrlProperty",
"className": "cseProperty col-xs-3",
"type": "select",
"templateOptions": {
"required": true,
"label": "Attribute",
"valueProp": "name",
"options": "$mappings.options.attributeWhitelist"
},
"asyncValidators": "$mappings.validators.attributeSelector"
}, {
"className": "col-xs-3",
"template": "<div class='searchengineinfo'><i class='glyphicon glyphicon-ok'></i> {{ model.infoMessages.detailUrlSelector }} </p><p>{{ model.infoMessages.detailUrlProperty}}</p></div>"
}]
}]
// ...
DuckieTV.controller('customSearchEngineDialogCtrl', ["$q", "$http", "FormlyLoader",
function($q, $http, FormlyLoader) {
var self = this;
FormlyLoader.setMapping('validators', {
searchResultsContainer: {
expression: function($viewValue, $modelValue, scope) {
return getCachedScraper().then(function(d) {
try {
var results = d.querySelectorAll($viewValue);
console.log("Results: ", results);
if (!results || results.length === 0) {
self.model.infoMessages[scope.options.key] = 'No results found.';
throw new Error("no results found");
} else {
firstResult = results[0];
self.model.infoMessages[scope.options.key] = results.length + ' results found.';
revalidateRowSelectors();
return true;
}
} catch (E) {
self.model.infoMessages[scope.options.key] = E.message;
throw E;
}
});
}
},
propertySelector: {
expression: function($viewValue, $modelValue, scope) {
console.warn(scope.options.key + " validate!");
return $q(function(resolve, reject) {
if (firstResult) {
try {
var property = self.model[scope.options.key.replace('Selector', 'Property')];
console.warn("Property for " + scope.options.key + ": " + property, $viewValue, firstResult.querySelector($viewValue), firstResult.querySelector($viewValue)[self.model[property]], firstResult.querySelector($viewValue).getAttribute(property));
var el = firstResult.querySelector($viewValue);
self.model.infoMessages[scope.options.key] = (property == 'href' || property == 'src') ? el.getAttribute(property) : el[property];
} catch (E) {
self.model.infoMessages[scope.options.key] = E.message;
}
resolve();
} else {
throw "no results.";
}
});
}
},
attributeSelector: {
expression: function($viewValue, $modelValue, scope) {
return $q(function(resolve) {
resolve();
setTimeout(function() {
revalidateRowSelectors();
});
});
}
},
loginPage: {
expression: function($viewValue, $modelValue, scope) {
return $http.get(self.model.mirror + $viewValue).then(function(result) {
self.model.infoMessages[scope.options.key] = 'Login page found';
}, function(err) {
throw "Page not found " + err.message;
});
}
},
loginTestSelector: {
expression: function($viewValue, $modelValue, scope) {
return getCachedScraper().then(function(d) {
try {
var results = d.querySelectorAll($viewValue);
console.log("Results: ", results);
if (!results || results.length === 0) {
self.model.infoMessages[scope.options.key] = 'No results found, but assuming loggedin';
} else {
self.model.infoMessages[scope.options.key] = 'Not loggedin test OK';
}
} catch (E) {
self.model.infoMessages[scope.options.key] = E.message;
throw E;
}
});
}
}
});
FormlyLoader.setMapping('options', {
attributeWhitelist: [{
name: 'href',
}, {
name: 'innerText',
}, {
name: 'title',
}, {
name: 'src',
}, {
name: 'alt',
}]
});
FormlyLoader.setMapping('modelOptions', {
keyup: {
"updateOn": "keyup",
"debounce": 200
}
});
FormlyLoader.load('CustomSearchEngine').then(function(fields) {
self.fields = fields;
console.log("Loaded!", fields);
});
//.
/**
* Formly loader and config mapper.
* use setMapping and $parse strings to map individual properties to functions and arrays to remove duplicaton.
*
*
* Example in customSearchEngineCtrl.js
*
*/
DuckieTV.factory('FormlyLoader', function($http, $parse) {
var config = {
basePath: 'templates/formly-forms/',
mappings: {}
};
function recursivePropertyMap(field) {
if (field.fieldGroup) {
field.fieldGroup = field.fieldGroup.map(recursivePropertyMap);
return field;
}
return processMappings(field);
}
function processMappings(field) {
Object.keys(field).map(function(key) {
if (angular.isObject(field[key])) {
field[key] = processMappings(field[key]);
} else if (field[key].toString().indexOf('$mappings') === 0) {
var getter = $parse(field[key].split('$mappings.')[1]);
field[key] = getter(config.mappings);
}
});
return field;
}
var service = {
setBasePath: function(path) {
config.basePath = path;
},
setMapping: function(key, option) {
config.mappings[key] = option;
},
load: function(form) {
return $http.get(config.basePath + form + '.json').then(function(result) {
return result.data.map(recursivePropertyMap);
});
}
};
return service;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.