Feedly Faviconize List

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            Feedly Faviconize List
// @namespace       jmln.tw
// @version         0.2.7
// @description     A user script to show feed favicons in Feedly Title-Only View.
// @author          Jimmy Lin
// @license         MIT
// @homepage        https://github.com/jmlntw/feedly-faviconize-list
// @supportURL      https://github.com/jmlntw/feedly-faviconize-list/issues
// @match           https://*.feedly.com/*
// @compatible      firefox
// @compatible      chrome
// @compatible      opera
// @run-at          document-end
// @grant           none
// ==/UserScript==

function addStyle (css) {
  const style = document.createElement('style')
  style.textContent = css
  document.head.appendChild(style)
  return style
}

addStyle(`
  .gm-favicon {
    width: 16px;
    height: 16px;
    margin: 0 6px 0 0;
    padding: 0;
    border-radius: 3px;
    vertical-align: top;
  }
`)

function awaitSelector (selector, root) {
  return new Promise((resolve, reject) => {
    try {
      const rootElement = root ?
        typeof root === 'string' ? document.querySelector(root) : root :
        document

      const findAndResolveElements = () => {
        const allElements = document.querySelectorAll(selector)
        const newElements = []
        const resolvedAttr = 'data-awaitselector-resolved'

        if (allElements.length > 0) {
          Array.prototype.slice.call(allElements)
            .filter(element => typeof element[resolvedAttr] === 'undefined')
            .forEach(element => {
              element[resolvedAttr] = true
              newElements.push(element)
            })

          if (newElements.length > 0) {
            observer.disconnect()
            resolve(newElements)
          }
        }
      }

      const observer = new MutationObserver(mutations => {
        const addedNodes = mutations.reduce((found, mutation) => {
          return found || mutation.addedNodes && mutation.addedNodes.length > 0
        })

        if (addedNodes) {
          findAndResolveElements()
        }
      })

      observer.observe(rootElement, {
        childList: true,
        subtree: true
      })

      findAndResolveElements()
    } catch (exception) {
      reject(exception)
    }
  })
}

function waitAwaitSelector (selector, root, callback) {
  (function awaiter () {
    const continueWatching = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : true

    if (continueWatching) {
      awaitSelector(selector, root).then(callback).then(awaiter)
    }
  }())
}

function createFavicon (url) {
  const domain = url.replace(/^https?:\/\/([^/:]+).*/i, '$1')
  const favicon = document.createElement('img')

  favicon.src = `https://www.google.com/s2/favicons?domain=${domain}&alt=feed`
  favicon.classList.add('gm-favicon')

  return favicon
}

awaitSelector('#feedlyPageFX', '#root').then(pages => {
  waitAwaitSelector('a.EntryMetadataSource[href]', pages[0], sources => {
    sources
      .filter(source => source.querySelector('.gm-favicon') === null)
      .forEach(source => {
        source.insertAdjacentElement('afterbegin', createFavicon(source.href))
      })
  })
})