Emoji favicons for the web
0
fork

Configure Feed

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

fix: cleanup

+59 -81
+6 -5
source/components/emoji_selector/components/custom_delete.tsx
··· 4 4 import type { Settings } from '../../../models/settings.ts'; 5 5 import type { SetRoute } from '../types.ts'; 6 6 7 - import { Fragment, h } from 'preact'; 7 + import { h } from 'preact'; 8 8 import { useCallback, useContext, useState } from 'preact/hooks'; 9 9 10 10 import { deleteEmoji, emoji } from '../../../models/emoji.ts'; ··· 27 27 <div className='emoji-group'> 28 28 {Object.keys(customEmojis) 29 29 .map((name) => { 30 - const emoji = customEmojis[name]; 31 30 return ( 32 31 <EmojiButton 33 32 className='emoji-selector-button' 34 33 onClick={async () => { 35 34 try { 36 35 if (confirm(`Delete ${name}?`)) { 37 - await deleteEmoji(emoji); 36 + await deleteEmoji(customEmojis[name]); 38 37 await saveToStorageBypassCache({ 39 38 ...cache, 40 39 customEmojiIds: cache.customEmojiIds 41 - .filter((desc: string) => desc !== emoji.description), 40 + .filter((desc: string) => 41 + desc !== customEmojis[name].description 42 + ), 42 43 }); 43 44 setRoute(ROUTE.DEFAULT); 44 45 } ··· 46 47 confirm(e); 47 48 } 48 49 }} 49 - emoji={emoji} 50 + emoji={customEmojis[name]} 50 51 /> 51 52 ); 52 53 })}
+1 -1
source/components/emoji_selector/components/popup.tsx
··· 3 3 import type { OnSelected, Route, SetRoute, SetSwitch } from '../types.ts'; 4 4 import type { Ref } from 'preact'; 5 5 6 - import { Fragment, h } from 'preact'; 6 + import { h } from 'preact'; 7 7 import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'; 8 8 9 9 import { emoji, emojiGroups, emojiGroupsArray } from '../../../models/emoji.ts';
+5 -2
source/components/list.tsx
··· 6 6 import { h } from 'preact'; 7 7 import { useRef } from 'preact/hooks'; 8 8 9 - import ListInput from './list_input.tsx'; 9 + import ListInput, { LIST_TYPE, ListType } from './list_input.tsx'; 10 + 11 + export type { ListType }; 12 + export { LIST_TYPE }; 10 13 11 14 export interface ListProps<Type> { 12 - type: 'FAVICON' | 'IGNORE'; 15 + type: ListType; 13 16 state: ListState<Favicon>; 14 17 } 15 18
+3 -11
source/components/list_input.tsx
··· 12 12 13 13 const IGNORE = 'IGNORE'; 14 14 const FAVICON = 'FAVICON'; 15 - type ListType = typeof IGNORE | typeof FAVICON; 16 - 17 - type Target = { 18 - matcher?: string; 19 - index: number; 20 - toDelete: boolean; 21 - }; 15 + export type ListType = typeof IGNORE | typeof FAVICON; 16 + export const LIST_TYPE: { [name: string]: ListType } = { IGNORE, FAVICON }; 22 17 23 18 interface ListInputProps { 24 19 autoFocus?: boolean; ··· 89 84 /> 90 85 91 86 <Only if={type === FAVICON}> 92 - <EmojiSelector 93 - emojiId={value?.emojiId} 94 - onSelected={onChangeEmoji} 95 - /> 87 + <EmojiSelector emojiId={value?.emojiId} onSelected={onChangeEmoji} /> 96 88 </Only> 97 89 98 90 <Only if={Boolean(deleteItem)}>
+11 -8
source/hooks/use_browser_storage.ts
··· 3 3 4 4 const { storage, runtime } = browserAPI; 5 5 6 - // deno-lint-ignore no-explicit-any 7 - type Storage = Record<string, any>; 8 - 9 - export interface BrowserStorage<Type extends Storage> { 6 + export interface BrowserStorage<Type> { 10 7 error?: string; 11 8 cache: Type; 12 9 loading: boolean; ··· 25 22 * - `saveCacheToStorage` saves that local data into browserStorage on a separate interaction 26 23 */ 27 24 type Keys = string | readonly string[]; 28 - export default function useBrowserStorage<Type extends Storage>( 25 + export interface Changes<Type> { 26 + [key: string]: { 27 + newValue?: Type; 28 + oldValue?: Type; 29 + }; 30 + } 31 + 32 + export default function useBrowserStorage<Type>( 29 33 keys: Keys, 30 34 defaultState: Type, 31 - ) { 35 + ): BrowserStorage<Type> { 32 36 const [error, setError] = useState<string>(); 33 37 const [cache, setCache] = useState<Type>(defaultState); 34 38 const [loading, setLoading] = useState<boolean>(true); 35 39 36 40 const updateState = useCallback( 37 - // deno-lint-ignore no-explicit-any 38 - async function (changes: void | { [key: string]: any }) { 41 + async function (changes: void | Changes<Type>) { 39 42 const keyArray = Array.isArray(keys) ? keys : [keys]; 40 43 const noChange = changes && 41 44 !keyArray.some((key) => Boolean(changes[key]));
+14 -18
source/hooks/use_list_state.ts
··· 1 1 import { useCallback, useEffect, useState } from 'preact/hooks'; 2 2 3 - // deno-lint-ignore no-explicit-any 4 - type ListItem = any; 5 - 6 3 export interface ListState<Type> { 7 4 contents: Type[]; 8 5 addItem: (item: Type) => void; ··· 10 7 deleteItem: (index: number) => void; 11 8 } 12 9 13 - export default (initialValue: ListItem[]) => { 14 - const [contents, setContents] = useState(initialValue); 10 + export default function useListState<Type>( 11 + initialValue: Type[], 12 + ): ListState<Type> { 13 + const [contents, setContents] = useState<Type[]>(initialValue); 15 14 16 15 useEffect(() => setContents(initialValue), [initialValue]); 17 16 18 17 return { 19 18 contents, 20 19 21 - addItem: useCallback((listItem: ListItem) => { 20 + addItem: useCallback((listItem: Type) => { 22 21 setContents([...contents, listItem]); 23 22 }, [contents]), 24 23 25 24 updateItem: useCallback( 26 - (indexToUpdate: number, updatedListItem: ListItem) => { 27 - const updatedListItems: ListItem[] = contents.map( 28 - (prevListItem, index) => 29 - index === indexToUpdate ? updatedListItem : prevListItem, 30 - ); 31 - setContents(updatedListItems); 25 + (indexToUpdate: number, newListItem: Type) => { 26 + const newList: Type[] = contents 27 + .map((oldListItem, index) => 28 + index === indexToUpdate ? newListItem : oldListItem 29 + ); 30 + setContents(newList); 32 31 }, 33 32 [contents], 34 33 ), 35 34 36 - deleteItem: useCallback((listItemIndex: number) => { 37 - const nextlistItems = contents.filter( 38 - (_, index) => index !== listItemIndex, 39 - ); 40 - setContents(nextlistItems); 35 + deleteItem: useCallback((itemIndex: number) => { 36 + setContents(contents.filter((_, index) => index !== itemIndex)); 41 37 }, [contents]), 42 38 }; 43 - }; 39 + }
+1 -3
source/models/storage_legacy.ts
··· 38 38 'skips', 39 39 ]; 40 40 41 - export function isSettingsV1( 42 - settings: unknown, 43 - ): settings is SettingsV1 { 41 + export function isSettingsV1(settings: unknown): settings is SettingsV1 { 44 42 if (typeof settings !== 'object' || settings == null) return false; 45 43 return ( 46 44 'flagReplaced' in settings ||
+14 -21
source/pages/favicons_page.tsx
··· 1 1 /* @jsx h */ 2 2 import type { BrowserStorage } from '../hooks/use_browser_storage.ts'; 3 + import type { Favicon } from '../models/favicon.ts'; 3 4 import type { Settings } from '../models/settings.ts'; 4 5 5 - import { Fragment, h } from 'preact'; 6 + import { h } from 'preact'; 6 7 import { useContext, useEffect } from 'preact/hooks'; 7 8 8 - import List from '../components/list.tsx'; 9 + import List, { LIST_TYPE } from '../components/list.tsx'; 9 10 import Only from '../components/only.tsx'; 10 11 import useListState from '../hooks/use_list_state.ts'; 11 12 import { SettingsContext } from '../models/settings.ts'; 12 13 import { t } from '../utilities/i18n.ts'; 13 14 14 - export interface FaviconsPageProps { 15 - default?: boolean; 16 - path?: string; 17 - save?: (e: Event) => void; 18 - } 19 - 20 - export default function FaviconsPage({ save }: FaviconsPageProps) { 15 + export default function FaviconsPage({ save }: { save?: (e: Event) => void }) { 21 16 const storage = useContext<BrowserStorage<Settings>>(SettingsContext); 22 17 const { siteList, ignoreList, features } = storage.cache; 23 18 const { enableSiteIgnore } = features; 24 - const siteListState = useListState(siteList); 25 - const ignoreListState = useListState(ignoreList); 19 + const siteListState = useListState<Favicon>(siteList); 20 + const ignoreListState = useListState<Favicon>(ignoreList); 26 21 27 22 useEffect(() => { 28 23 if (storage) { ··· 36 31 return ( 37 32 <form onSubmit={save}> 38 33 <h1>{t('faviconListTitle')}</h1> 39 - <List type='FAVICON' state={siteListState} /> 34 + <List type={LIST_TYPE.FAVICON} state={siteListState} /> 40 35 41 36 <Only if={Boolean(enableSiteIgnore || ignoreListState.contents?.length)}> 42 - <Fragment> 43 - <h1> 44 - {t('ignoreListTitle')} 45 - <Only if={!enableSiteIgnore}> 46 - <span style={{ opacity: 0.5 }}>(Disabled)</span> 47 - </Only> 48 - </h1> 37 + <h1> 38 + {t('ignoreListTitle')} 39 + <Only if={!enableSiteIgnore}> 40 + <span style={{ opacity: 0.5 }}>(Disabled)</span> 41 + </Only> 42 + </h1> 49 43 50 - <List type='IGNORE' state={ignoreListState} /> 51 - </Fragment> 44 + <List type={LIST_TYPE.IGNORE} state={ignoreListState} /> 52 45 </Only> 53 46 54 47 <button type='submit' children={t('saveLabel')} className='save' />
+4 -12
source/pages/settings_page.tsx
··· 2 2 import type { BrowserStorage } from '../hooks/use_browser_storage.ts'; 3 3 import type { Settings } from '../models/settings.ts'; 4 4 5 - import { Fragment, h } from 'preact'; 5 + import { h } from 'preact'; 6 6 import { useCallback, useContext } from 'preact/hooks'; 7 7 8 8 import Checkbox, { Target } from '../components/checkbox.tsx'; 9 9 import Only from '../components/only.tsx'; 10 - import { DEFAULT_SETTINGS, SettingsContext } from '../models/settings.ts'; 10 + import { SettingsContext } from '../models/settings.ts'; 11 11 import { AUTOSELECTOR_VERSION } from '../utilities/favicon_autoselector.ts'; 12 12 import { t } from '../utilities/i18n.ts'; 13 13 14 - export interface SettingsProps { 15 - default?: boolean; 16 - path?: string; 17 - save?: (e: Event) => void; 18 - } 19 - 20 - const SettingsPage = ({ save }: SettingsProps) => { 14 + export default function SettingsPage({ save }: { save?: (e: Event) => void }) { 21 15 const storage = useContext<BrowserStorage<Settings>>(SettingsContext); 22 16 const { cache, setCache } = storage; 23 17 const { autoselectorVersion, features } = cache; ··· 90 84 <button type='submit' children={t('saveLabel')} className='save' /> 91 85 </form> 92 86 ); 93 - }; 94 - 95 - export default SettingsPage; 87 + }