diff --git a/packages/api/src/services/user_personalization.ts b/packages/api/src/services/user_personalization.ts index 66e6ea6e3..cf8075b34 100644 --- a/packages/api/src/services/user_personalization.ts +++ b/packages/api/src/services/user_personalization.ts @@ -131,7 +131,7 @@ const userDefaultShortcuts = async (userId: string): Promise => { id: label.id, type: 'label', name: label.name, - section: 'library', + section: 'search', label: label, filter: `in:all label:"${label.name}"`, } @@ -166,7 +166,7 @@ const userDefaultShortcuts = async (userId: string): Promise => { id: search.id, type: 'search', name: search.name, - section: 'library', + section: 'search', filter: search.filter, } }), diff --git a/packages/web/components/elements/DropdownElements.tsx b/packages/web/components/elements/DropdownElements.tsx index 2c6400684..0fb5ec969 100644 --- a/packages/web/components/elements/DropdownElements.tsx +++ b/packages/web/components/elements/DropdownElements.tsx @@ -6,6 +6,7 @@ import { Trigger, Arrow, Label, + Portal, } from '@radix-ui/react-dropdown-menu' import { PopperContentProps } from '@radix-ui/react-popover' import { CSS } from '@stitches/react' @@ -181,24 +182,26 @@ export function Dropdown( > {triggerElement} - { - // remove focus from dropdown - ;(document.activeElement as HTMLElement).blur() - }} - side={side} - sideOffset={sideOffset} - align={align ? align : 'center'} - alignOffset={alignOffset} - onCloseAutoFocus={(event) => { - event.preventDefault() - }} - > - {labelText && {labelText}} - {children} - - + + { + // remove focus from dropdown + ;(document.activeElement as HTMLElement).blur() + }} + side={side} + sideOffset={sideOffset} + align={align ? align : 'center'} + alignOffset={alignOffset} + onCloseAutoFocus={(event) => { + event.preventDefault() + }} + > + {labelText && {labelText}} + {children} + + + ) } diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index 50ad458fa..6c2fd86cb 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -39,11 +39,10 @@ const textVariants = { }, settingsSection: { fontWeight: '600', - fontSize: '17px', + fontSize: '22px', fontFamily: '$inter', color: '$grayText', m: '0px', - my: '15px', marginBlockStart: '0px', marginBlockEnd: '0px', }, diff --git a/packages/web/lib/navigations.ts b/packages/web/lib/navigations.ts index 97d275d17..5953e9b9b 100644 --- a/packages/web/lib/navigations.ts +++ b/packages/web/lib/navigations.ts @@ -1 +1 @@ -export const DEFAULT_HOME_PATH = `/l/home` +export const DEFAULT_HOME_PATH = `/home` diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 148e2a34a..637f1bd05 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -69,6 +69,10 @@ const moduleExports = { source: '/subscriptions', destination: '/l/subscriptions', }) + rewrites.push({ + source: '/search', + destination: '/l/search', + }) rewrites.push({ source: '/archive', destination: '/l/archive', diff --git a/packages/web/pages/l/[section].tsx b/packages/web/pages/l/[section].tsx index 2d5e3e4e8..522563e89 100644 --- a/packages/web/pages/l/[section].tsx +++ b/packages/web/pages/l/[section].tsx @@ -84,6 +84,17 @@ export default function Home(): JSX.Element { showNavigationMenu={showNavigationMenu} /> ) + case 'search': + return ( + { + console.log('item: ', item) + return true + }} + showNavigationMenu={showNavigationMenu} + /> + ) case 'archive': return ( shortcut.id !== targetId) + .map((shortcut) => ({ + ...shortcut, + children: removeShortcutById(shortcut.children, targetId), + })) +} + +type ListAction = 'RESET' | 'ADD_ITEM' | 'REMOVE_ITEM' + export default function Shortcuts(): JSX.Element { - const { data: shortcuts, isLoading } = useGetShortcuts() + const { data, isLoading } = useGetShortcuts() + const setShortcuts = useSetShortcuts() + + const listReducer = ( + state: { state: string; items: Shortcut[] }, + action: { + type: ListAction + item?: Shortcut + items?: Shortcut[] + } + ) => { + switch (action.type) { + case 'RESET': { + return { state: 'CURRENT', items: action.items ?? [] } + } + case 'ADD_ITEM': { + const item = action.item + if (!item) { + return state + } + const existing = state.items.find( + (existing) => existing.type == item.type && existing.id == item.id + ) + if (existing) { + return state + } + state.items.push(item) + setShortcuts.mutate({ + shortcuts: [...state.items], + }) + return { state: 'CURRENT', items: [...state.items] } + } + case 'REMOVE_ITEM': { + const item = action.item + console.log('removing item: ', item) + if (!item) { + return state + } + const updated = removeShortcutById(state.items, item.id) ?? [] + setShortcuts.mutate({ + shortcuts: [...updated], + }) + return { state: 'CURRENT', items: [...updated] } + } + default: + throw new Error('unknown action') + } + } + + const [shortcuts, dispatchList] = useReducer(listReducer, { + state: 'INITIAL', + items: [], + }) + const shortcutIds = useMemo(() => { - if (shortcuts) { - return flattenShortcuts(shortcuts) + if (shortcuts.items) { + return flattenShortcuts(shortcuts.items) } return [] - }, [shortcuts]) + }, [shortcuts.items]) - console.log('shortcutIds: ', shortcutIds) + useEffect(() => { + if (!isLoading) { + console.log('data: ', data) + dispatchList({ type: 'RESET', items: data }) + } + }, [data]) return ( - + + + + + ) } @@ -85,187 +178,7 @@ export const SectionSeparator = styled(Separator, { type ListProps = { shortcutIds: string[] - // dispatchList: (arg: { type: ListAction; item?: Shortcut | undefined }) => void -} - -const AvailableItems = (props: ListProps): JSX.Element => { - const { data: labels } = useGetLabels() - const { data: savedSearches } = useGetSavedSearches() - const { subscriptions } = useGetSubscriptionsQuery() - - const sortedLabels = useMemo(() => { - if (!labels) { - return [] - } - return labels.sort((a, b) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) - ) - }, [labels]) - - const sortedSubscriptions = useMemo(() => { - if (!subscriptions) { - return [] - } - return subscriptions.sort((a, b) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) - ) - }, [subscriptions]) - - const sortedsavedSearches = useMemo(() => { - if (!savedSearches) { - return [] - } - return savedSearches.sort((a, b) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) - ) - }, [savedSearches]) - - const searchSelected = useCallback( - (search: SavedSearch) => { - return !!props.shortcutIds.find((shortcutId) => shortcutId == search.id) - }, - [props] - ) - - const labelSelected = useCallback( - (label: Label) => { - return !!props.shortcutIds.find((shortcutId) => shortcutId == label.id) - }, - [props] - ) - - const subscriptionSelected = useCallback( - (subscription: Subscription) => { - return !!props.shortcutIds.find( - (shortcutId) => shortcutId == subscription.id - ) - }, - [props] - ) - - console.log('sortedsavedSearchesL: ', sortedsavedSearches) - - return ( - - {/* Available items */} - - - {/* - Labels - {sortedLabels.map((label) => { - console.log('label: ', label) - return ( - - ) - })} - - - Subscriptions - {sortedSubscriptions.map((subscription) => { - console.log('subscription: ', subscription) - return ( - - ) - })} */} - - ) + dispatchList: (arg: { type: ListAction; item?: Shortcut | undefined }) => void } const SavedSearches = (props: ListProps) => { @@ -275,23 +188,263 @@ const SavedSearches = (props: ListProps) => { if (!savedSearches) { return [] } - return savedSearches.sort((a, b) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) + return ( + savedSearches + // .filter((search) => props.shortcutIds.indexOf(search.id) === -1) + .sort((a, b) => + a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) + ) ) - }, [savedSearches]) + }, [props, savedSearches]) + + const isChecked = useCallback( + (shortcutId: string) => { + return props.shortcutIds.indexOf(shortcutId) !== -1 + }, + [props.shortcutIds] + ) + return ( - <> - {/* Saved Searches */} - {/* */} - {!isLoading && - (savedSearches ?? []).map((search) => { - return ( - - {search.name} - - ) - })} - {/* */} - + + Saved Searches + + {!isLoading && + (sortedsavedSearches ?? []).map((search) => { + return ( + + {search.name} + + { + const item = { + id: search.id, + type: 'search' as ShortcutType, + name: search.name, + section: 'search', + filter: search.filter, + } + if (checked) { + props.dispatchList({ + type: 'ADD_ITEM', + item, + }) + } else { + props.dispatchList({ + type: 'REMOVE_ITEM', + item, + }) + } + }} + /> + + + ) + })} + + ) } + +const Subscriptions = (props: ListProps) => { + const { data: subscriptions, isLoading } = useGetSubscriptions({}) + + const sortedSubscriptions = useMemo(() => { + if (!subscriptions) { + return [] + } + return subscriptions.sort((a, b) => + a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) + ) + }, [props, subscriptions]) + + const isChecked = useCallback( + (shortcutId: string) => { + return props.shortcutIds.indexOf(shortcutId) !== -1 + }, + [props.shortcutIds] + ) + + return ( + + Subscriptions + + {!isLoading && + (sortedSubscriptions ?? []).map((subscription) => { + return ( + + {subscription.icon ? ( + + ) : subscription.type == SubscriptionType.NEWSLETTER ? ( + + ) : ( + + )} + {subscription.name} + + { + const item: Shortcut = { + id: subscription.id, + section: 'subscriptions', + name: subscription.name, + icon: subscription.icon, + type: + subscription.type == SubscriptionType.NEWSLETTER + ? 'newsletter' + : 'feed', + filter: + subscription.type == SubscriptionType.NEWSLETTER + ? `subscription:\"${escapeQuotes( + subscription.name + )}\"` + : `rss:\"${subscription.url}\"`, + } + if (checked) { + props.dispatchList({ + type: 'ADD_ITEM', + item, + }) + } else { + props.dispatchList({ + type: 'REMOVE_ITEM', + item, + }) + } + }} + /> + + + ) + })} + + + ) +} + +const Labels = (props: ListProps) => { + const { data: labels, isLoading } = useGetLabels() + + const sortedLabels = useMemo(() => { + if (!labels) { + return [] + } + return labels.sort((a, b) => + a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) + ) + }, [props, labels]) + + const isChecked = useCallback( + (shortcutId: string) => { + return props.shortcutIds.indexOf(shortcutId) !== -1 + }, + [props.shortcutIds] + ) + + return ( + + Labels + + {!isLoading && + (sortedLabels ?? []).map((label) => { + return ( + + + + {label.name} + + + { + const item: Shortcut = { + id: label.id, + type: 'label', + label: label, + section: 'search', + name: label.name, + filter: `in:all label:\"${escapeQuotes(label.name)}\"`, + } + if (checked) { + props.dispatchList({ + type: 'ADD_ITEM', + item, + }) + } else { + props.dispatchList({ + type: 'REMOVE_ITEM', + item, + }) + } + }} + /> + + + ) + })} + + + ) +} + +type SwitchBoxProps = { + checked: boolean + setChecked: (checked: boolean) => void +} +const SwitchBox = (props: SwitchBoxProps) => { + return ( + { + props.setChecked(checked) + }} + > + + + ) +} + +const SwitchRoot = styled(Switch.Root, { + all: 'unset', + width: 42, + height: 25, + backgroundColor: '$thBorderColor', + borderRadius: '9999px', + position: 'relative', + WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)', + '&:focus': { boxShadow: `0 0 0 2px $thBorderColor` }, + '&[data-state="checked"]': { backgroundColor: '#4BB543' }, +}) + +const SwitchThumb = styled(Switch.Thumb, { + display: 'block', + width: 21, + height: 21, + backgroundColor: '$thTextContrast2', + borderRadius: '9999px', + transition: 'transform 100ms', + transform: 'translateX(2px)', + willChange: 'transform', + '&[data-state="checked"]': { transform: 'translateX(19px)' }, +})