File indexing completed on 2024-05-19 06:00:28

0001 (function ($) {
0002   'use strict';
0003 
0004   var RSS = function (target, url, options, callback) {
0005     this.target       = target;
0006 
0007     this.url          = url;
0008     this.html         = [];
0009     this.effectQueue  = [];
0010 
0011     this.options = $.extend({
0012       ssl: false,
0013       host: 'www.feedrapp.info',
0014       limit: null,
0015       key: null,
0016       layoutTemplate: '<ul>{entries}</ul>',
0017       entryTemplate: '<li><a href="{url}">[{author}@{date}] {title}</a><br/>{shortBodyPlain}</li>',
0018       tokens: {},
0019       outputMode: 'json',
0020       dateFormat: 'dddd MMM Do',
0021       dateLocale: 'en',
0022       effect: 'show',
0023       offsetStart: false,
0024       offsetEnd: false,
0025       error: function () {
0026         console.log('jQuery RSS: url doesn\'t link to RSS-Feed');
0027       },
0028       onData: function () {},
0029       success: function () {}
0030     }, options || {});
0031 
0032     this.callback = callback || this.options.success;
0033   };
0034 
0035   RSS.htmlTags = [
0036     'doctype', 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 'noscript',
0037     'body', 'article', 'nav', 'aside', 'section', 'header', 'footer', 'h1-h6', 'hgroup', 'address',
0038     'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'figure', 'figcaption',
0039     'div', 'table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'col', 'colgroup',
0040     'form', 'fieldset', 'legend', 'label', 'input', 'button', 'select', 'datalist', 'optgroup',
0041     'option', 'textarea', 'keygen', 'output', 'progress', 'meter', 'details', 'summary', 'command',
0042     'menu', 'del', 'ins', 'img', 'iframe', 'embed', 'object', 'param', 'video', 'audio', 'source',
0043     'canvas', 'track', 'map', 'area', 'a', 'em', 'strong', 'i', 'b', 'u', 's', 'small', 'abbr', 'q',
0044     'cite', 'dfn', 'sub', 'sup', 'time', 'code', 'kbd', 'samp', 'var', 'mark', 'bdi', 'bdo', 'ruby',
0045     'rt', 'rp', 'span', 'br', 'wbr'
0046   ];
0047 
0048   RSS.prototype.load = function (callback) {
0049     var apiProtocol = 'http' + (this.options.ssl ? 's' : '');
0050     var apiHost     = apiProtocol + '://' + this.options.host;
0051     var apiUrl      = apiHost + '?callback=?&q=' + encodeURIComponent(this.url);
0052 
0053     // set limit to offsetEnd if offset has been set
0054     if (this.options.offsetStart && this.options.offsetEnd) {
0055       this.options.limit = this.options.offsetEnd;
0056     }
0057 
0058     if (this.options.limit !== null) {
0059       apiUrl += '&num=' + this.options.limit;
0060     }
0061 
0062     if (this.options.key !== null) {
0063       apiUrl += '&key=' + this.options.key;
0064     }
0065 
0066     $.getJSON(apiUrl, callback);
0067   };
0068 
0069   RSS.prototype.render = function () {
0070     var self = this;
0071 
0072     this.load(function (data) {
0073       try {
0074         self.feed    = data.responseData.feed;
0075         self.entries = data.responseData.feed.entries;
0076       } catch (e) {
0077         self.entries = [];
0078         self.feed    = null;
0079         return self.options.error.call(self);
0080       }
0081 
0082       var html = self.generateHTMLForEntries();
0083 
0084       self.target.append(html.layout);
0085 
0086       if (html.entries.length !== 0) {
0087         if ($.isFunction(self.options.onData)) {
0088           self.options.onData.call(self);
0089         }
0090 
0091         var container = $(html.layout).is('entries') ? html.layout : $('entries', html.layout);
0092 
0093         self.appendEntriesAndApplyEffects(container, html.entries);
0094       }
0095 
0096       if (self.effectQueue.length > 0) {
0097         self.executeEffectQueue(self.callback);
0098       } else if ($.isFunction(self.callback)) {
0099         self.callback.call(self);
0100       }
0101     });
0102   };
0103 
0104   RSS.prototype.appendEntriesAndApplyEffects = function (target, entries) {
0105     var self = this;
0106 
0107     $.each(entries, function (idx, entry) {
0108       var $html = self.wrapContent(entry);
0109 
0110       if (self.options.effect === 'show') {
0111         target.before($html);
0112       } else {
0113         $html.css({ display: 'none' });
0114         target.before($html);
0115         self.applyEffect($html, self.options.effect);
0116       }
0117     });
0118 
0119     target.remove();
0120   };
0121 
0122   RSS.prototype.generateHTMLForEntries = function () {
0123     var self   = this;
0124     var result = { entries: [], layout: null };
0125 
0126     $(this.entries).each(function () {
0127       var entry       = this;
0128       var offsetStart = self.options.offsetStart;
0129       var offsetEnd   = self.options.offsetEnd;
0130       var evaluatedString;
0131 
0132       // offset required
0133       if (offsetStart && offsetEnd) {
0134         if (index >= offsetStart && index <= offsetEnd) {
0135           if (self.isRelevant(entry, result.entries)) {
0136             evaluatedString = self.evaluateStringForEntry(
0137               self.options.entryTemplate, entry
0138             );
0139 
0140             result.entries.push(evaluatedString);
0141           }
0142         }
0143       } else {
0144         // no offset
0145         if (self.isRelevant(entry, result.entries)) {
0146           evaluatedString = self.evaluateStringForEntry(
0147             self.options.entryTemplate, entry
0148           );
0149 
0150           result.entries.push(evaluatedString);
0151         }
0152       }
0153     });
0154 
0155     if (!!this.options.entryTemplate) {
0156       // we have an entryTemplate
0157       result.layout = this.wrapContent(
0158         this.options.layoutTemplate.replace('{entries}', '<entries></entries>')
0159       );
0160     } else {
0161       // no entryTemplate available
0162       result.layout = this.wrapContent('<div><entries></entries></div>');
0163     }
0164 
0165     return result;
0166   };
0167 
0168   RSS.prototype.wrapContent = function (content) {
0169     if (($.trim(content).indexOf('<') !== 0)) {
0170       // the content has no html => create a surrounding div
0171       return $('<div>' + content + '</div>');
0172     } else {
0173       // the content has html => don't touch it
0174       return $(content);
0175     }
0176   };
0177 
0178   RSS.prototype.applyEffect = function ($element, effect, callback) {
0179     var self = this;
0180 
0181     switch (effect) {
0182       case 'slide':
0183         $element.slideDown('slow', callback);
0184         break;
0185       case 'slideFast':
0186         $element.slideDown(callback);
0187         break;
0188       case 'slideSynced':
0189         self.effectQueue.push({ element: $element, effect: 'slide' });
0190         break;
0191       case 'slideFastSynced':
0192         self.effectQueue.push({ element: $element, effect: 'slideFast' });
0193         break;
0194     }
0195   };
0196 
0197   RSS.prototype.executeEffectQueue = function (callback) {
0198     var self = this;
0199 
0200     this.effectQueue.reverse();
0201 
0202     var executeEffectQueueItem = function () {
0203       var item = self.effectQueue.pop();
0204 
0205       if (item) {
0206         self.applyEffect(item.element, item.effect, executeEffectQueueItem);
0207       } else if (callback) {
0208         callback();
0209       }
0210     };
0211 
0212     executeEffectQueueItem();
0213   };
0214 
0215   RSS.prototype.evaluateStringForEntry = function (string, entry) {
0216     var result = string;
0217     var self   = this;
0218 
0219     $(string.match(/(\{.*?\})/g)).each(function () {
0220       var token = this.toString();
0221 
0222       result = result.replace(token, self.getValueForToken(token, entry));
0223     });
0224 
0225     return result;
0226   };
0227 
0228   RSS.prototype.isRelevant = function (entry, entries) {
0229     var tokenMap = this.getTokenMap(entry);
0230 
0231     if (this.options.filter) {
0232       if (this.options.filterLimit && (this.options.filterLimit === entries.length)) {
0233         return false;
0234       } else {
0235         return this.options.filter(entry, tokenMap);
0236       }
0237     } else {
0238       return true;
0239     }
0240   };
0241 
0242   RSS.prototype.getFormattedDate = function (dateString) {
0243     // If a custom formatting function is provided, use that.
0244     if (this.options.dateFormatFunction) {
0245       return this.options.dateFormatFunction(dateString);
0246     } else if (typeof moment !== 'undefined') {
0247       // If moment.js is available and dateFormatFunction is not overriding it,
0248       // use it to format the date.
0249       var date = moment(new Date(dateString));
0250 
0251       if (date.locale) {
0252         date = date.locale(this.options.dateLocale);
0253       } else {
0254         date = date.lang(this.options.dateLocale);
0255       }
0256 
0257       return date.format(this.options.dateFormat);
0258     } else {
0259       // If all else fails, just use the date as-is.
0260       return dateString;
0261     }
0262   };
0263 
0264   RSS.prototype.getTokenMap = function (entry) {
0265     if (!this.feedTokens) {
0266       var feed = JSON.parse(JSON.stringify(this.feed));
0267 
0268       delete feed.entries;
0269       this.feedTokens = feed;
0270     }
0271 
0272     return $.extend({
0273       feed:      this.feedTokens,
0274       url:       entry.link,
0275       author:    entry.author,
0276       date:      this.getFormattedDate(entry.publishedDate),
0277       title:     entry.title,
0278       body:      entry.content,
0279       shortBody: entry.contentSnippet,
0280 
0281       bodyPlain: (function (entry) {
0282         var result = entry.content
0283           .replace(/<script[\\r\\\s\S]*<\/script>/mgi, '')
0284           .replace(/<\/?[^>]+>/gi, '');
0285 
0286         for (var i = 0; i < RSS.htmlTags.length; i++) {
0287           result = result.replace(new RegExp('<' + RSS.htmlTags[i], 'gi'), '');
0288         }
0289 
0290         return result;
0291       })(entry),
0292 
0293       shortBodyPlain: entry.contentSnippet.replace(/<\/?[^>]+>/gi, ''),
0294       index:          $.inArray(entry, this.entries),
0295       totalEntries:   this.entries.length,
0296 
0297       teaserImage:    (function (entry) {
0298         try {
0299           return entry.content.match(/(<img.*?>)/gi)[0];
0300         }
0301         catch (e) {
0302           return '';
0303         }
0304       })(entry),
0305 
0306       teaserImageUrl: (function (entry) {
0307         try {
0308           return entry.content.match(/(<img.*?>)/gi)[0].match(/src="(.*?)"/)[1];
0309         }
0310         catch (e) {
0311           return '';
0312         }
0313       })(entry)
0314     }, this.options.tokens);
0315   };
0316 
0317   RSS.prototype.getValueForToken = function (_token, entry) {
0318     var tokenMap = this.getTokenMap(entry);
0319     var token    = _token.replace(/[\{\}]/g, '');
0320     var result   = tokenMap[token];
0321 
0322     if (typeof result !== 'undefined') {
0323       return ((typeof result === 'function') ? result(entry, tokenMap) : result);
0324     } else {
0325       throw new Error('Unknown token: ' + _token + ', url:' + this.url);
0326     }
0327   };
0328 
0329   $.fn.rss = function (url, options, callback) {
0330     new RSS(this, url, options, callback).render();
0331     return this; // Implement chaining
0332   };
0333 })(jQuery);