1(function ($) { 2 'use strict'; 3 4 var RSS = function (target, url, options, callback) { 5 this.target = target; 6 7 this.url = url; 8 this.html = []; 9 this.effectQueue = []; 10 11 this.options = $.extend({ 12 ssl: false, 13 host: 'www.feedrapp.info', 14 limit: null, 15 key: null, 16 layoutTemplate: '<ul>{entries}</ul>', 17 entryTemplate: '<li><a href="{url}">[{author}@{date}] {title}</a><br/>{shortBodyPlain}</li>', 18 tokens: {}, 19 outputMode: 'json', 20 dateFormat: 'dddd MMM Do', 21 dateLocale: 'en', 22 effect: 'show', 23 offsetStart: false, 24 offsetEnd: false, 25 error: function () { 26 console.log('jQuery RSS: url doesn\'t link to RSS-Feed'); 27 }, 28 onData: function () {}, 29 success: function () {} 30 }, options || {}); 31 32 this.callback = callback || this.options.success; 33 }; 34 35 RSS.htmlTags = [ 36 'doctype', 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 'noscript', 37 'body', 'article', 'nav', 'aside', 'section', 'header', 'footer', 'h1-h6', 'hgroup', 'address', 38 'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'figure', 'figcaption', 39 'div', 'table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'col', 'colgroup', 40 'form', 'fieldset', 'legend', 'label', 'input', 'button', 'select', 'datalist', 'optgroup', 41 'option', 'textarea', 'keygen', 'output', 'progress', 'meter', 'details', 'summary', 'command', 42 'menu', 'del', 'ins', 'img', 'iframe', 'embed', 'object', 'param', 'video', 'audio', 'source', 43 'canvas', 'track', 'map', 'area', 'a', 'em', 'strong', 'i', 'b', 'u', 's', 'small', 'abbr', 'q', 44 'cite', 'dfn', 'sub', 'sup', 'time', 'code', 'kbd', 'samp', 'var', 'mark', 'bdi', 'bdo', 'ruby', 45 'rt', 'rp', 'span', 'br', 'wbr' 46 ]; 47 48 RSS.prototype.load = function (callback) { 49 var apiProtocol = 'http' + (this.options.ssl ? 's' : ''); 50 var apiHost = apiProtocol + '://' + this.options.host; 51 var apiUrl = apiHost + '?callback=?&q=' + encodeURIComponent(this.url); 52 53 // set limit to offsetEnd if offset has been set 54 if (this.options.offsetStart && this.options.offsetEnd) { 55 this.options.limit = this.options.offsetEnd; 56 } 57 58 if (this.options.limit !== null) { 59 apiUrl += '&num=' + this.options.limit; 60 } 61 62 if (this.options.key !== null) { 63 apiUrl += '&key=' + this.options.key; 64 } 65 66 $.getJSON(apiUrl, callback); 67 }; 68 69 RSS.prototype.render = function () { 70 var self = this; 71 72 this.load(function (data) { 73 try { 74 self.feed = data.responseData.feed; 75 self.entries = data.responseData.feed.entries; 76 } catch (e) { 77 self.entries = []; 78 self.feed = null; 79 return self.options.error.call(self); 80 } 81 82 var html = self.generateHTMLForEntries(); 83 84 self.target.append(html.layout); 85 86 if (html.entries.length !== 0) { 87 if ($.isFunction(self.options.onData)) { 88 self.options.onData.call(self); 89 } 90 91 var container = $(html.layout).is('entries') ? html.layout : $('entries', html.layout); 92 93 self.appendEntriesAndApplyEffects(container, html.entries); 94 } 95 96 if (self.effectQueue.length > 0) { 97 self.executeEffectQueue(self.callback); 98 } else if ($.isFunction(self.callback)) { 99 self.callback.call(self); 100 } 101 }); 102 }; 103 104 RSS.prototype.appendEntriesAndApplyEffects = function (target, entries) { 105 var self = this; 106 107 $.each(entries, function (idx, entry) { 108 var $html = self.wrapContent(entry); 109 110 if (self.options.effect === 'show') { 111 target.before($html); 112 } else { 113 $html.css({ display: 'none' }); 114 target.before($html); 115 self.applyEffect($html, self.options.effect); 116 } 117 }); 118 119 target.remove(); 120 }; 121 122 RSS.prototype.generateHTMLForEntries = function () { 123 var self = this; 124 var result = { entries: [], layout: null }; 125 126 $(this.entries).each(function () { 127 var entry = this; 128 var offsetStart = self.options.offsetStart; 129 var offsetEnd = self.options.offsetEnd; 130 var evaluatedString; 131 132 // offset required 133 if (offsetStart && offsetEnd) { 134 if (index >= offsetStart && index <= offsetEnd) { 135 if (self.isRelevant(entry, result.entries)) { 136 evaluatedString = self.evaluateStringForEntry( 137 self.options.entryTemplate, entry 138 ); 139 140 result.entries.push(evaluatedString); 141 } 142 } 143 } else { 144 // no offset 145 if (self.isRelevant(entry, result.entries)) { 146 evaluatedString = self.evaluateStringForEntry( 147 self.options.entryTemplate, entry 148 ); 149 150 result.entries.push(evaluatedString); 151 } 152 } 153 });
154 155 if (!!this.options.entryTemplate) { 156 // we have an entryTemplate 157 result.layout = this.wrapContent( 158 this.options.layoutTemplate.replace('{entries}', '<entries></entries>') 159 ); 160 } else { 161 // no entryTemplate available 162 result.layout = this.wrapContent('<div><entries></entries></div>'); 163 } 164 165 return result; 166 }; 167 168 RSS.prototype.wrapContent = function (content) { 169 if (($.trim(content).indexOf('<') !== 0)) { 170 // the content has no html => create a surrounding div 171 return $('<div>' + content + '</div>'); 172 } else { 173 // the content has html => don't touch it 174 return $(content); 175 } 176 }; 177 178 RSS.prototype.applyEffect = function ($element, effect, callback) { 179 var self = this; 180 181 switch (effect) { 182 case 'slide': 183 $element.slideDown('slow', callback); 184 break; 185 case 'slideFast': 186 $element.slideDown(callback); 187 break; 188 case 'slideSynced': 189 self.effectQueue.push({ element: $element, effect: 'slide' }); 190 break; 191 case 'slideFastSynced': 192 self.effectQueue.push({ element: $element, effect: 'slideFast' }); 193 break; 194 } 195 }; 196 197 RSS.prototype.executeEffectQueue = function (callback) { 198 var self = this; 199 200 this.effectQueue.reverse(); 201 202 var executeEffectQueueItem = function () { 203 var item = self.effectQueue.pop(); 204 205 if (item) { 206 self.applyEffect(item.element, item.effect, executeEffectQueueItem); 207 } else if (callback) { 208 callback(); 209 } 210 }; 211 212 executeEffectQueueItem(); 213 }; 214 215 RSS.prototype.evaluateStringForEntry = function (string, entry) { 216 var result = string; 217 var self = this; 218 219 $(string.match(/(\{.*?\})/g)).each(function () { 220 var token = this.toString(); 221 222 result = result.replace(token, self.getValueForToken(token, entry)); 223 }); 224 225 return result; 226 }; 227 228 RSS.prototype.isRelevant = function (entry, entries) { 229 var tokenMap = this.getTokenMap(entry); 230 231 if (this.options.filter) { 232 if (this.options.filterLimit && (this.options.filterLimit === entries.length)) { 233 return false; 234 } else { 235 return this.options.filter(entry, tokenMap); 236 } 237 } else { 238 return true; 239 } 240 }; 241 242 RSS.prototype.getFormattedDate = function (dateString) { 243 // If a custom formatting function is provided, use that. 244 if (this.options.dateFormatFunction) { 245 return this.options.dateFormatFunction(dateString); 246 } else if (typeof moment !== 'undefined') { 247 // If moment.js is available and dateFormatFunction is not overriding it, 248 // use it to format the date. 249 var date = moment(new Date(dateString)); 250 251 if (date.locale) { 252 date = date.locale(this.options.dateLocale); 253 } else { 254 date = date.lang(this.options.dateLocale); 255 } 256 257 return date.format(this.options.dateFormat); 258 } else { 259 // If all else fails, just use the date as-is. 260 return dateString; 261 } 262 }; 263 264 RSS.prototype.getTokenMap = function (entry) { 265 if (!this.feedTokens) { 266 var feed = JSON.parse(JSON.stringify(this.feed)); 267 268 delete feed.entries; 269 this.feedTokens = feed; 270 } 271 272 return $.extend({ 273 feed: this.feedTokens, 274 url: entry.link, 275 author: entry.author, 276 date: this.getFormattedDate(entry.publishedDate), 277 title: entry.title, 278 body: entry.content, 279 shortBody: entry.contentSnippet, 280 281 bodyPlain: (function (entry) { 282 var result = entry.content 283 .replace(/<script[\\r\\\s\S]*<\/script>/mgi, '') 284 .replace(/<\/?[^>]+>/gi, ''); 285 286 for (var i = 0; i < RSS.htmlTags.length; i++) { 287 result = result.replace(new RegExp('<' + RSS.htmlTags[i], 'gi'), ''); 288 } 289 290 return result; 291 })(entry), 292 293 shortBodyPlain: entry.contentSnippet.replace(/<\/?[^>]+>/gi, ''), 294 index: $.inArray(entry, this.entries), 295 totalEntries: this.entries.length, 296 297 teaserImage: (function (entry) { 298 try { 299 return entry.content.match(/(<img.*?>)/gi)[0]; 300 } 301 catch (e) { 302 return ''; 303 } 304 })(entry), 305 306 teaserImageUrl: (function (entry) { 307 try { 308 return entry.content.match(/(<img.*?>)/gi)[0].match(/src="(.*?)"/)[1]; 309 } 310 catch (e) { 311 return ''; 312 } 313 })(entry) 314 }, this.options.tokens); 315 }; 316 317 RSS.prototype.getValueForToken = function (_token, entry) { 318 var tokenMap = this.getTokenMap(entry); 319 var token = _token.replace(/[\{\}]/g, ''); 320 var result = tokenMap[token]; 321 322 if (typeof result !== 'undefined') { 323 return ((typeof result === 'function') ? result(entry, tokenMap) : result); 324 } else { 325 throw new Error('Unknown token: ' + _token + ', url:' + this.url); 326 } 327 }; 328 329 $.fn.rss = function (url, options, callback) { 330 new RSS(this, url, options, callback).render(); 331 return this; // Implement chaining 332 }; 333})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.