Feedly Faviconize List

A user script to show feed favicons in Feedly Title-Only View.

  1. // ==UserScript==
  2. // @name Feedly Faviconize List
  3. // @namespace jmln.tw
  4. // @version 0.2.7
  5. // @description A user script to show feed favicons in Feedly Title-Only View.
  6. // @author Jimmy Lin
  7. // @license MIT
  8. // @homepage https://github.com/jmlntw/feedly-faviconize-list
  9. // @supportURL https://github.com/jmlntw/feedly-faviconize-list/issues
  10. // @match https://*.feedly.com/*
  11. // @compatible firefox
  12. // @compatible chrome
  13. // @compatible opera
  14. // @run-at document-end
  15. // @grant none
  16. // ==/UserScript==
  17.  
  18. function addStyle (css) {
  19. const style = document.createElement('style')
  20. style.textContent = css
  21. document.head.appendChild(style)
  22. return style
  23. }
  24.  
  25. addStyle(`
  26. .gm-favicon {
  27. width: 16px;
  28. height: 16px;
  29. margin: 0 6px 0 0;
  30. padding: 0;
  31. border-radius: 3px;
  32. vertical-align: top;
  33. }
  34. `)
  35.  
  36. function awaitSelector (selector, root) {
  37. return new Promise((resolve, reject) => {
  38. try {
  39. const rootElement = root ?
  40. typeof root === 'string' ? document.querySelector(root) : root :
  41. document
  42.  
  43. const findAndResolveElements = () => {
  44. const allElements = document.querySelectorAll(selector)
  45. const newElements = []
  46. const resolvedAttr = 'data-awaitselector-resolved'
  47.  
  48. if (allElements.length > 0) {
  49. Array.prototype.slice.call(allElements)
  50. .filter(element => typeof element[resolvedAttr] === 'undefined')
  51. .forEach(element => {
  52. element[resolvedAttr] = true
  53. newElements.push(element)
  54. })
  55.  
  56. if (newElements.length > 0) {
  57. observer.disconnect()
  58. resolve(newElements)
  59. }
  60. }
  61. }
  62.  
  63. const observer = new MutationObserver(mutations => {
  64. const addedNodes = mutations.reduce((found, mutation) => {
  65. return found || mutation.addedNodes && mutation.addedNodes.length > 0
  66. })
  67.  
  68. if (addedNodes) {
  69. findAndResolveElements()
  70. }
  71. })
  72.  
  73. observer.observe(rootElement, {
  74. childList: true,
  75. subtree: true
  76. })
  77.  
  78. findAndResolveElements()
  79. } catch (exception) {
  80. reject(exception)
  81. }
  82. })
  83. }
  84.  
  85. function waitAwaitSelector (selector, root, callback) {
  86. (function awaiter () {
  87. const continueWatching = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : true
  88.  
  89. if (continueWatching) {
  90. awaitSelector(selector, root).then(callback).then(awaiter)
  91. }
  92. }())
  93. }
  94.  
  95. function createFavicon (url) {
  96. const domain = url.replace(/^https?:\/\/([^/:]+).*/i, '$1')
  97. const favicon = document.createElement('img')
  98.  
  99. favicon.src = `https://www.google.com/s2/favicons?domain=${domain}&alt=feed`
  100. favicon.classList.add('gm-favicon')
  101.  
  102. return favicon
  103. }
  104.  
  105. awaitSelector('#feedlyPageFX', '#root').then(pages => {
  106. waitAwaitSelector('a.EntryMetadataSource[href]', pages[0], sources => {
  107. sources
  108. .filter(source => source.querySelector('.gm-favicon') === null)
  109. .forEach(source => {
  110. source.insertAdjacentElement('afterbegin', createFavicon(source.href))
  111. })
  112. })
  113. })