├── .gitignore ├── typography.js ├── overrides ├── index.js ├── arrayFnsPicks.js ├── holidayGiftGuide.js └── giftGuide2019.js ├── package.json ├── index.html ├── README.md ├── nextUntil.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /typography.js: -------------------------------------------------------------------------------- 1 | import Typography from "typography"; 2 | import oceanBeach from "typography-theme-ocean-beach"; 3 | 4 | const typography = new Typography(oceanBeach); 5 | 6 | typography.injectStyles(); 7 | -------------------------------------------------------------------------------- /overrides/index.js: -------------------------------------------------------------------------------- 1 | import arrayFns from './arrayFnsPicks'; 2 | import holidayGiftGuide from './holidayGiftGuide'; 3 | import giftGuide2019 from './giftGuide2019'; 4 | 5 | export default [arrayFns, holidayGiftGuide, giftGuide2019]; -------------------------------------------------------------------------------- /overrides/arrayFnsPicks.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '20 JavaScript Array and Object Methods to make you a better developer', 3 | picks: `
Scott: Tea Haus
6 |14 | Contribute to the source code on Github! 17 |
18 |Loading...
19 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Syntax.fm Sick Picks 2 | 3 | This project was created to provide a single place to view all of the Sick Picks 4 | recommended on the [Syntax podcast.](https://syntax.fm/) hosted by [Wes Bos](https://twitter.com/wesbos) and 5 | [Scott Tolinksi](https://twitter.com/stolinski). 6 | 7 | The app parses the Syntax RSS feed and searches for a header in each episode 8 | containing a list of "Sick Picks". If the header is found, all following HTML 9 | until the next header is added to the site. 10 | 11 | [Check out the site on Netlify.](https://sick-picks.netlify.com/) 12 | 13 | ## Setup 14 | 15 | Ensure you have [Node.js installed.](https://nodejs.org/en/download/package-manager/) 16 | 17 | In your terminal, run this to install all npm dependencies. 18 | ```bash 19 | npm install 20 | ``` 21 | 22 | In your terminal, run this to start running the web app locally. 23 | ```bash 24 | npm start 25 | ``` 26 | 27 | Open the webapp in a browser by navigating to `localhost:1234`. 28 | 29 | ## Contributing 30 | 31 | If there are open Issues, a pull request is always welcome. 32 | 33 | If you discover an problem, or have a suggestion while using the site, 34 | please [open an issue on Github.](https://github.com/rmkubik/sick-picks/issues) 35 | -------------------------------------------------------------------------------- /nextUntil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get all following siblings of each element up to but not including the element matched by the selector 3 | * @param {Node} elem The element 4 | * @param {String} selector The selector to stop at 5 | * @param {String} filter The selector to match siblings against [optional] 6 | * @return {Array} The siblings 7 | */ 8 | export default function nextUntil (elem, selector, filter) { 9 | // matches() polyfill 10 | if (!Element.prototype.matches) { 11 | Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 12 | } 13 | 14 | // Setup siblings array 15 | var siblings = []; 16 | 17 | // Get the next sibling element 18 | elem = elem.nextElementSibling; 19 | 20 | // As long as a sibling exists 21 | while (elem) { 22 | 23 | // If we've reached our match, bail 24 | if (elem.matches(selector)) break; 25 | 26 | // If filtering by a selector, check if the sibling matches 27 | if (filter && !elem.matches(filter)) { 28 | elem = elem.nextElementSibling; 29 | continue; 30 | } 31 | 32 | // Otherwise, push it to the siblings array 33 | siblings.push(elem); 34 | 35 | // Get the next sibling element 36 | elem = elem.nextElementSibling; 37 | 38 | } 39 | 40 | return siblings; 41 | }; 42 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import "babel-polyfill"; 2 | import "./typography"; 3 | import Parser from "rss-parser"; 4 | import dateFormat from "date-fns/format"; 5 | import nextUntil from "./nextUntil"; 6 | import overrides from "./overrides"; 7 | 8 | const isSickPick = header => 9 | header.innerText 10 | .toLowerCase() 11 | .trim() 12 | .match(/si.*ck.*pi.*ck/); 13 | 14 | const rssParser = new Parser(); 15 | 16 | (async () => { 17 | try { 18 | const feedResponse = await fetch("https://feed.syntax.fm/rss"); 19 | const feedString = await feedResponse.text(); 20 | const feed = await rssParser.parseString(feedString); 21 | let sickPicks = []; 22 | feed.items.forEach(item => { 23 | const sickPick = document.createElement("div"); 24 | 25 | const header = document.createElement("h2"); 26 | header.innerText = item.title; 27 | sickPick.appendChild(header); 28 | 29 | const pubDate = document.createElement("p"); 30 | pubDate.innerText = dateFormat(new Date(item.pubDate), "MMMM D, YYYY"); 31 | sickPick.appendChild(pubDate); 32 | 33 | const domParser = new DOMParser(); 34 | const doc = domParser.parseFromString(item.content, "text/html"); 35 | const headers = doc.querySelectorAll("h2"); 36 | const headerTag = [...headers].find(isSickPick); 37 | 38 | if ( 39 | headerTag || 40 | overrides.some(override => override.title === item.title) 41 | ) { 42 | let picks; 43 | if (overrides.some(override => override.title === item.title)) { 44 | const override = domParser.parseFromString( 45 | overrides.find(override => override.title === item.title).picks, 46 | "text/html" 47 | ); 48 | picks = [...override.body.children]; 49 | } else { 50 | picks = nextUntil(headerTag, "h2"); 51 | } 52 | 53 | picks.forEach(pick => { 54 | if (pick instanceof Node) { 55 | sickPick.appendChild(pick); 56 | } 57 | }); 58 | 59 | sickPicks.push(sickPick); 60 | } 61 | }); 62 | 63 | document.querySelector("#loading").style.display = "none"; 64 | 65 | sickPicks.forEach(sickPick => { 66 | document.body.appendChild(sickPick); 67 | }); 68 | } catch (error) { 69 | document.querySelector("#loading").style.display = "none"; 70 | document.querySelector("#error").style.display = "block"; 71 | } 72 | })(); 73 | -------------------------------------------------------------------------------- /overrides/holidayGiftGuide.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Holiday Gift Guide', 3 | picks: `3:57 - Foodie Gifts
4 |11:27 - Experiences
23 |18:15 - Subscriptions
31 |23:07 - Books / Audible
44 |29:47 - Clothes
55 |39:08 - Gadgets
87 |48:00 - Smart Home
128 |54:40 - Grooming and Wellness
141 |58:57 - High End
148 |3:48 - Gadgets
16:59 - Smart Home Automation
27:14 - Laptop Stands
29:59 - Phone Cases
30:52 - Mechanical Keyboards
34:34 - Mice
35:27 - Tripods
37:04 - Cameras
38:06 - Experiential
40:08 - Food
49:24 - Grooming & Wellness
` 4 | }; 5 | --------------------------------------------------------------------------------