├── .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: `` 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sickpicks2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-polyfill": "^6.26.0", 14 | "date-fns": "^1.30.1", 15 | "rss-parser": "^3.5.4", 16 | "typography": "^0.16.18", 17 | "typography-theme-ocean-beach": "^0.16.18" 18 | }, 19 | "devDependencies": { 20 | "parcel": "^1.10.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Syntax.fm Sick Picks 6 | 7 | 12 |

Syntax.fm Sick Picks

13 |

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 | 22 |

11:27 - Experiences

23 | 30 |

18:15 - Subscriptions

31 | 43 |

23:07 - Books / Audible

44 | 54 |

29:47 - Clothes

55 | 86 |

39:08 - Gadgets

87 | 127 |

48:00 - Smart Home

128 | 140 |

54:40 - Grooming and Wellness

141 | 147 |

58:57 - High End

148 | ` 153 | }; 154 | -------------------------------------------------------------------------------- /overrides/giftGuide2019.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '2019 Gift Guide', 3 | picks: `

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 | --------------------------------------------------------------------------------