Emoji favicons for the web
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add or remove override from the popup menu

+120 -93
+16 -4
source/background.ts
··· 12 12 import browserAPI from './utilities/browser_api.ts'; 13 13 14 14 const autoselector = new Autoselector(); 15 - const settings: Settings = 16 - (await browserAPI.storage.sync.get(STORAGE_KEYS) as Settings | void) || 17 - defaultSettings; 15 + let settings: Settings = defaultSettings; 18 16 19 - const { siteList = [], features = {} } = settings || defaultSettings; 17 + 18 + await updateCache() 19 + 20 + browserAPI.storage.onChanged.addListener(async () => { 21 + await updateCache(); 22 + }); 20 23 21 24 browserAPI.tabs.onUpdated.addListener( 22 25 (tabId: number, _: TabChangeInfo, tab: Tab) => { 26 + const { siteList = [], features = {} } = settings; 27 + 23 28 if (!tab.url) return; 24 29 25 30 const shouldOverride = (siteList || []).some( ··· 35 40 } 36 41 }, 37 42 ); 43 + 44 + async function updateCache() { 45 + try { 46 + const storedSettings: Settings = await browserAPI.storage.sync.get(STORAGE_KEYS) as Settings; 47 + if (storedSettings) settings = storedSettings; 48 + } catch {} 49 + }
+13
source/content_script.ts
··· 9 9 import { appendFaviconLink } from './utilities/favicon_helpers.ts'; 10 10 import browserAPI from './utilities/browser_api.ts'; 11 11 12 + browserAPI.storage.onChanged.addListener(async (changes) => { 13 + if (changes?.siteList) { 14 + const { newValue = [], oldValue = [] } = changes?.siteList || {}; 15 + const newDiff = newValue.filter(includesCurrUrl); 16 + const oldDiff = oldValue.filter(includesCurrUrl); 17 + if (newDiff.length !== oldDiff.length) location.reload(); 18 + } 19 + }); 20 + 21 + function includesCurrUrl(val:string) { 22 + return (new RegExp(val)).test(location.href); 23 + } 24 + 12 25 browserAPI.runtime.onMessage.addListener(({ 13 26 favicon, 14 27 shouldOverride,
+27 -44
source/hooks/use_browser_storage.ts
··· 1 1 import { useCallback, useEffect, useState } from 'preact/hooks'; 2 2 import browserAPI from '../utilities/browser_api.ts'; 3 - import { 4 - requestPermissionToAllSites, 5 - requestPermissionToSites, 6 - } from '../utilities/permissions.ts'; 7 - 8 3 const { storage, runtime } = browserAPI; 9 4 10 5 // deno-lint-ignore no-explicit-any ··· 14 9 error?: string; 15 10 cache?: Type; 16 11 loading: boolean; 17 - setCache: (nextCache: Partial<Type>) => void; 12 + setCache: (nextCache: Partial<Type>, saveImmediately?: boolean) => void; 18 13 saveCacheToStorage: () => Promise<void>; 19 14 } 20 15 ··· 43 38 }); 44 39 }, []); 45 40 41 + const saveToStorage = useCallback( 42 + (next: Partial<Type> | void): Promise<void> => { 43 + if (!next) return Promise.resolve(); 44 + 45 + return storage.sync.set(next) 46 + .then(() => { 47 + if (runtime?.lastError?.message) { 48 + setError(runtime?.lastError?.message); 49 + throw new Error(runtime?.lastError?.message); 50 + } 51 + }); 52 + }, 53 + [], 54 + ); 55 + 46 56 const result: BrowserStorage<Type> = { 47 57 error, 48 58 cache, 49 59 loading, 50 60 51 - setCache: useCallback((nextCache: Partial<Type>): void => { 52 - const nextStorage = { ...cache, ...nextCache }; 53 - setCache(nextStorage as Type); 54 - }, [cache, setCache]), 61 + setCache: useCallback( 62 + (nextCache: Partial<Type>, saveImmediately: boolean = false): void => { 63 + const nextStorage = { ...cache, ...nextCache }; 64 + setCache(nextStorage as Type); 65 + if (saveImmediately) saveToStorage(nextStorage); 66 + }, 67 + [cache, setCache], 68 + ), 55 69 56 - async saveCacheToStorage(): Promise<void> { 57 - const nextStorage = cache; 58 - if (!nextStorage) return; 59 - 60 - const origins = nextStorage.siteList 61 - .map(function validateUrl(site: string) { 62 - try { 63 - return new URL(site).origin + '/'; 64 - } catch (e) { 65 - console.error(e); 66 - return false; 67 - } /* Not a URL */ 68 - }) 69 - .filter(Boolean); 70 - 71 - const hasNonUrlPattern = origins.length === nextStorage.siteList.length; 72 - const hasPermission = hasNonUrlPattern 73 - ? await requestPermissionToSites(origins) 74 - : await requestPermissionToAllSites(); 75 - 76 - if (!hasPermission) return Promise.reject('No Permission Given'); 77 - 78 - return new Promise((resolve, reject) => 79 - storage.sync.set(nextStorage) 80 - .then(() => { 81 - if (runtime?.lastError?.message) { 82 - setError(runtime?.lastError?.message); 83 - reject(runtime?.lastError?.message); 84 - } else { 85 - resolve(); 86 - } 87 - }) 88 - ); 89 - }, 70 + saveCacheToStorage: useCallback((): Promise<void> => { 71 + return saveToStorage(cache); 72 + }, [cache]), 90 73 }; 91 74 92 75 return result;
+13 -12
source/hooks/use_status.ts
··· 4 4 5 5 export default function useStatus( 6 6 error: string, 7 - saveCacheToStorage: () => Promise<void>, 7 + save: (...args: any[]) => Promise<void>, 8 8 ) { 9 9 const [status, setStatus] = useState<string>(''); 10 10 ··· 13 13 setTimeout(() => setStatus(''), STATUS_TIME); 14 14 }, [error]); 15 15 16 - const saveSettings = useCallback(async () => { 17 - try { 18 - await saveCacheToStorage(); 19 - setStatus('Successfully Saved'); 20 - } catch (e) { 21 - setStatus(`Error: ${e}`); 22 - } 23 - setTimeout(() => setStatus(''), STATUS_TIME); 24 - }, [saveCacheToStorage]); 25 - 26 - return { saveSettings, status }; 16 + return { 17 + status, 18 + save: useCallback(async (...args: any[]) => { 19 + try { 20 + await save(...args); 21 + setStatus('Successfully Saved'); 22 + } catch (e) { 23 + setStatus(`Error: ${e}`); 24 + } 25 + setTimeout(() => setStatus(''), STATUS_TIME); 26 + }, [save]), 27 + }; 27 28 }
+6 -6
source/options.tsx
··· 20 20 const route = useRoute(); 21 21 const storage = useBrowserStorage<Settings>(STORAGE_KEYS); 22 22 const { error = '', loading, saveCacheToStorage } = storage; 23 - const { status, saveSettings } = useStatus(error || '', saveCacheToStorage); 23 + const { status, save } = useStatus(error || '', saveCacheToStorage); 24 24 25 - const save = useCallback((e: Event) => { 25 + const saveOptions = useCallback((e: Event) => { 26 26 e.preventDefault(); 27 - saveSettings(); 28 - }, [saveSettings]); 27 + save(); 28 + }, [save]); 29 29 30 30 if (loading || error) return <div />; 31 31 ··· 35 35 <div className='page'> 36 36 <Switch 37 37 value={route} 38 - defaultCase={<FaviconsPage save={save} storage={storage} />} 38 + defaultCase={<FaviconsPage save={saveOptions} storage={storage} />} 39 39 cases={{ 40 - '#settings': <SettingsPage save={save} storage={storage} />, 40 + '#settings': <SettingsPage save={saveOptions} storage={storage} />, 41 41 '#about': <AboutPage />, 42 42 }} 43 43 />
+40 -21
source/popup.tsx
··· 1 1 /* @jsx h */ 2 + import type { Tab } from './utilities/browser_api_interface/mod.ts'; 2 3 3 4 import { h, render } from 'preact'; 4 - import { useCallback, useEffect, useState } from 'preact/hooks'; 5 + import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'; 5 6 import useBrowserStorage from './hooks/use_browser_storage.ts'; 6 7 import browserAPI from './utilities/browser_api.ts'; 7 8 import useStatus from './hooks/use_status.ts'; 8 - import { Settings, STORAGE_KEYS, Tab } from './types.ts'; 9 + import { Settings, STORAGE_KEYS } from './types.ts'; 9 10 10 11 const queryOptions = { active: true }; 11 12 12 13 const App = () => { 13 14 const storage = useBrowserStorage<Settings>(STORAGE_KEYS); 14 - const { cache, error = '', loading, setCache, saveCacheToStorage } = storage; 15 - const { status, saveSettings } = useStatus(error || '', saveCacheToStorage); 15 + const { cache, error = '', loading, setCache } = storage; 16 + const [currTab, setCurrTab] = useState<Tab | void>(); 17 + const { favIconUrl = '', url = '' } = currTab || {}; 18 + 19 + useEffect(() => { 20 + browserAPI.tabs.query(queryOptions) 21 + .then(([tab]: Tab[]) => setCurrTab(tab)); 22 + 23 + browserAPI.storage.onChanged.addListener(async () => { 24 + const [ tab ] = await browserAPI.tabs.query(queryOptions) 25 + setCurrTab(tab); 26 + }); 27 + }, [cache]); 28 + 29 + const { status, save } = useStatus( 30 + error || '', 31 + useCallback(async (add: boolean) => { 32 + if (!url) return; 33 + const origin = (new URL(url)).origin; 34 + const siteList = cache?.siteList || []; 35 + const nextList = siteList.filter((filter) => filter !== origin); 36 + if (add) nextList.push(origin); 37 + setCache({ siteList: nextList }, true); 38 + }, [url, cache, setCache]), 39 + ); 16 40 17 41 const addToOverrides = useCallback(() => { 18 - const siteList = cache?.siteList || []; 19 - browserAPI.tabs.query(queryOptions) 20 - .then(([{ url }]: Tab[]) => { 21 - if (url) { 22 - const origin = (new URL(url)).origin; 23 - const nextList = siteList 24 - .filter((filter) => filter !== origin) 25 - .concat(origin); 26 - setCache({ siteList: nextList }); 27 - saveSettings(); 28 - } 29 - }); 30 - }, [storage]); 42 + save(true); 43 + }, [cache, url, setCache]); 44 + 45 + const removeFromOverrides = useCallback(() => { 46 + save(false); 47 + }, [cache, url, setCache]); 31 48 32 49 const goToOptions = useCallback(() => { 33 50 browserAPI.runtime.openOptionsPage(); 34 51 }, []); 35 52 53 + if (loading) return <div>loading...</div> 54 + 36 55 return ( 37 56 <div className='page'> 38 57 <h1>Favioli</h1> 58 + <p>Current Favicon: {favIconUrl ? <img className="favicon-icon" src={favIconUrl} width={20} height={20} /> : null}</p> 39 59 <button onClick={addToOverrides}> 40 - Change Favicon 60 + Override Favicon 41 61 </button> 42 - <button onClick={goToOptions}> 43 - Options 44 - </button> 62 + <button onClick={removeFromOverrides}>Remove Override</button> 63 + <button onClick={goToOptions}>Options</button> 45 64 <div id='status'>{status}</div> 46 65 </div> 47 66 );
+4
source/static/styles/popup.css
··· 1 + .favicon-icon { 2 + vertical-align: middle; 3 + padding-left: 5px; 4 + }
-5
source/types.ts
··· 12 12 }; 13 13 } 14 14 15 - export interface Tab { 16 - favIconUrl?: string; 17 - url?: string; 18 - } 19 - 20 15 export const defaultSettings: Settings = { 21 16 siteList: [], 22 17 ignoreList: [],
+1 -1
source/utilities/favicon_helpers.ts
··· 22 22 // Already appended favicon; just update 23 23 if (appendedFavicon) { 24 24 appendedFavicon.setAttribute('href', faviconURL); 25 - } else if (await doesSiteHaveFavicon() === false) { 25 + } else if (shouldOverride || await doesSiteHaveFavicon() === false) { 26 26 appendedFavicon = head.appendChild( 27 27 createLink(faviconURL, ICON_SIZE, 'image/png'), 28 28 );