From 2615a0d33ecb8832287647125880d5e60101e368 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 16 Feb 2024 14:41:49 +0800 Subject: [PATCH 01/23] Add new nav icons, fixes for highlights view on smaller screen --- .../elements/icons/HighlightsIcon.tsx | 44 ++++--- .../templates/homeFeed/HighlightsLayout.tsx | 41 +++--- .../templates/homeFeed/HomeFeedContainer.tsx | 2 + .../templates/navMenu/LibraryMenu.tsx | 123 +++++++++++++++++- .../web/components/tokens/stitches.config.ts | 6 +- 5 files changed, 179 insertions(+), 37 deletions(-) diff --git a/packages/web/components/elements/icons/HighlightsIcon.tsx b/packages/web/components/elements/icons/HighlightsIcon.tsx index ef882dce2..10684b273 100644 --- a/packages/web/components/elements/icons/HighlightsIcon.tsx +++ b/packages/web/components/elements/icons/HighlightsIcon.tsx @@ -4,38 +4,48 @@ import { IconProps } from './IconProps' import React from 'react' -export class FollowingIcon extends React.Component { +export class HighlightsIcon extends React.Component { render() { const size = (this.props.size || 26).toString() const color = (this.props.color || '#2A2A2A').toString() return ( + diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index b28a172ca..1b66e1f91 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -20,23 +20,24 @@ import { import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard' import { NotebookContent } from '../article/Notebook' import { EmptyHighlights } from './EmptyHighlights' -import { DEFAULT_HEADER_HEIGHT } from './HeaderSpacer' import { highlightsAsMarkdown } from './HighlightItem' +import { MenuHeaderButton } from './LibraryHeader' type HighlightItemsLayoutProps = { items: LibraryItem[] viewer: UserBasicData | undefined + showFilterMenu: boolean + setShowFilterMenu: (show: boolean) => void + gridContainerRef: React.RefObject } export function HighlightItemsLayout( props: HighlightItemsLayoutProps ): JSX.Element { - // const headerHeight = useGetHeaderHeight() - const [currentItem, setCurrentItem] = useState( - undefined - ) + const [currentItem, setCurrentItem] = + useState(undefined) const listReducer = ( state: LibraryItem[], @@ -106,10 +107,7 @@ export function HighlightItemsLayout( @@ -122,15 +120,12 @@ export function HighlightItemsLayout( + distribution="start" + > + + + + )} diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index b6399f6fd..69144d824 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -20,6 +20,10 @@ import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon' import Link from 'next/link' import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon' import { NavMenuFooter } from './Footer' +import { FollowingIcon } from '../../elements/icons/FollowingIcon' +import { HomeIcon } from '../../elements/icons/HomeIcon' +import { LibraryIcon } from '../../elements/icons/LibraryIcon' +import { HighlightsIcon } from '../../elements/icons/HighlightsIcon' export const LIBRARY_LEFT_MENU_WIDTH = '275px' @@ -110,13 +114,14 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { css={{ width: '100%', px: '25px', - pb: '25px', + pb: '17px', pt: '4.5px', lineHeight: '1', }} > + @@ -139,6 +144,48 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { ) } +const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { + return ( + + } + /> + } + /> + } + /> + } + /> + + ) +} + function SavedSearches( props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined } ): JSX.Element { @@ -175,7 +222,7 @@ function SavedSearches( return ( @@ -400,6 +447,78 @@ function MenuPanel(props: MenuPanelProps): JSX.Element { ) } +type NavButtonProps = { + text: string + icon: ReactNode + + filterTerm: string + searchTerm: string | undefined + + applySearchQuery: (searchTerm: string) => void + setShowFilterMenu: (show: boolean) => void +} + +function NavButton(props: NavButtonProps): JSX.Element { + const isInboxFilter = (filter: string) => { + return filter === '' || filter === 'in:inbox' + } + const selected = useMemo(() => { + if (isInboxFilter(props.filterTerm) && !props.searchTerm) { + return true + } + return props.searchTerm === props.filterTerm + }, [props.searchTerm, props.filterTerm]) + + return ( + { + props.applySearchQuery(props.filterTerm) + props.setShowFilterMenu(false) + e.preventDefault() + }} + > + {props.icon} + {props.text} + + ) +} + type FilterButtonProps = { text: string diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index a27c06931..4f9c41ec9 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -181,11 +181,12 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = thLibrarySearchbox: '#FCFCFC', thLibraryMenuPrimary: '#3D3D3D', thLibraryMenuSecondary: '#3D3D3D', - thLibraryMenuUnselected: '#898989', + thLibraryMenuUnselected: '#3D3D3D', thLibrarySelectionColor: '#FFEA9F', thLibraryNavigationMenuFooter: '#EFEADE', thLibraryMenuFooterHover: '#FFFFFF', thFormInput: '#EBEBEB', + thHomeIcon: '#2A2A2A', thHeaderIconRing: '#D9D9D9', thHeaderIconInner: '#898989', @@ -305,12 +306,13 @@ const darkThemeSpec = { thLibrarySearchbox: '#3D3D3D', thLibraryMenuPrimary: '#EBEBEB', thLibraryMenuSecondary: '#EBEBEB', - thLibraryMenuUnselected: '#898989', + thLibraryMenuUnselected: 'white', thLibrarySelectionColor: '#3D3D3D', thLibraryNavigationMenuFooter: '#3D3D3D', thLibraryMenuFooterHover: '#6A6968', searchActiveOutline: '#866D15', thFormInput: '#3D3D3D', + thHomeIcon: '#FFFFFF', thHeaderIconRing: '#3D3D3D', thHeaderIconInner: '#D9D9D9', From 2aa7c1a7d422da368253ff0d105d9afcc00bd0ec Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 16 Feb 2024 15:34:39 +0800 Subject: [PATCH 02/23] Fix reader header offset, add labels icon to highlights --- .../elements/icons/FollowingIcon.tsx | 12 +++--- .../templates/article/HighlightsLayer.tsx | 20 +++++---- .../templates/reader/ReaderHeader.tsx | 1 + .../web/lib/highlights/highlightGenerator.ts | 42 ++++++++++++++----- .../web/lib/highlights/highlightHelpers.ts | 21 ++++++++++ packages/web/styles/articleInnerStyling.css | 22 +++++----- 6 files changed, 85 insertions(+), 33 deletions(-) diff --git a/packages/web/components/elements/icons/FollowingIcon.tsx b/packages/web/components/elements/icons/FollowingIcon.tsx index ef882dce2..e9fca6c04 100644 --- a/packages/web/components/elements/icons/FollowingIcon.tsx +++ b/packages/web/components/elements/icons/FollowingIcon.tsx @@ -22,20 +22,20 @@ export class FollowingIcon extends React.Component { d="M12.5 4.5L4.5 8.5L12.5 12.5L20.5 8.5L12.5 4.5Z" fill={color} stroke={color} - stroke-linecap="round" - stroke-linejoin="round" + strokeLinecap="round" + strokeLinejoin="round" /> diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index d5bb8b0bb..dd1ed5bf1 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -12,6 +12,7 @@ import type { Highlight } from '../../../lib/networking/fragments/highlightFragm import { getHighlightElements, highlightIdAttribute, + highlightLabelIdAttribute, highlightNoteIdAttribute, SelectionAttributes, } from '../../../lib/highlights/highlightHelpers' @@ -79,15 +80,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 }) const [currentHighlightIdx, setCurrentHighlightIdx] = useState(0) - const [focusedHighlight, setFocusedHighlight] = useState< - Highlight | undefined - >(undefined) + const [focusedHighlight, setFocusedHighlight] = + useState(undefined) const [selectionData, setSelectionData] = useSelection(highlightLocations) - const [labelsTarget, setLabelsTarget] = useState( - undefined - ) + const [labelsTarget, setLabelsTarget] = + useState(undefined) const [ confirmDeleteHighlightWithNoteId, @@ -388,6 +387,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { highlight: highlight, highlightModalAction: 'addComment', }) + } else if ((target as Element).hasAttribute(highlightLabelIdAttribute)) { + const id = (target as HTMLSpanElement).getAttribute( + highlightLabelIdAttribute + ) + const highlight = highlights.find(($0) => $0.id === id) + setFocusedHighlight(highlight) + setLabelsTarget(highlight) } else { window?.webkit?.messageHandlers.viewerAction?.postMessage({ actionID: 'pageTapped', @@ -395,7 +401,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { setFocusedHighlight(undefined) } }, - [openNoteModal, highlights] + [openNoteModal, highlights, setLabelsTarget] ) const handleDoubleClick = useCallback( diff --git a/packages/web/components/templates/reader/ReaderHeader.tsx b/packages/web/components/templates/reader/ReaderHeader.tsx index 992152222..0f6bb88f4 100644 --- a/packages/web/components/templates/reader/ReaderHeader.tsx +++ b/packages/web/components/templates/reader/ReaderHeader.tsx @@ -26,6 +26,7 @@ export function ReaderHeader(props: ReaderHeaderProps): JSX.Element { left: '0', zIndex: 1, pt: '0px', + pb: '15px', position: 'fixed', width: '100%', height: DEFAULT_HEADER_HEIGHT, diff --git a/packages/web/lib/highlights/highlightGenerator.ts b/packages/web/lib/highlights/highlightGenerator.ts index 19d1131bb..a74b33185 100644 --- a/packages/web/lib/highlights/highlightGenerator.ts +++ b/packages/web/lib/highlights/highlightGenerator.ts @@ -4,7 +4,9 @@ import type { Highlight } from '../networking/fragments/highlightFragment' import { interpolationSearch } from './interpolationSearch' import { highlightIdAttribute, + highlightLabelIdAttribute, highlightNoteIdAttribute, + labelsImage, noteImage, } from './highlightHelpers' @@ -80,10 +82,18 @@ function nodeAttributesFromHighlight( const patch = highlight.patch const id = highlight.id const withNote = !!highlight.annotation + const withLabels = (highlight.labels?.length ?? 0) > 0 const tooltip = undefined const customColor = highlight.color - return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip) + return makeHighlightNodeAttributes( + patch, + id, + withNote, + withLabels, + customColor, + tooltip + ) } /** @@ -100,6 +110,7 @@ export function makeHighlightNodeAttributes( patch: string, id: string, withNote: boolean, + withLabels: boolean, customColor?: string, tooltip?: string ): HighlightNodeAttributes { @@ -183,12 +194,10 @@ export function makeHighlightNodeAttributes( startingTextNodeIndex++ } if (withNote && lastElement) { - lastElement.classList.add('last_element') - const svg = noteImage(customColor) svg.setAttribute(highlightNoteIdAttribute, id) - const ctr = document.createElement('div') + const ctr = document.createElement('span') ctr.className = 'highlight_note_button' ctr.appendChild(svg) ctr.setAttribute(highlightNoteIdAttribute, id) @@ -197,6 +206,19 @@ export function makeHighlightNodeAttributes( lastElement.appendChild(ctr) } + if (withLabels && lastElement) { + const svg = labelsImage(customColor) + svg.setAttribute(highlightLabelIdAttribute, id) + + const ctr = document.createElement('span') + ctr.className = 'highlight_label_button' + ctr.appendChild(svg) + ctr.setAttribute(highlightLabelIdAttribute, id) + ctr.setAttribute('width', '14px') + ctr.setAttribute('height', '14px') + + lastElement.appendChild(ctr) + } return { prefix, @@ -209,10 +231,10 @@ export function makeHighlightNodeAttributes( /** * Given a text selection by user, annotate the article around the selection and - * produce a {@link https://github.com/google/diff-match-patch | diff patch} - * + * produce a {@link https://github.com/google/diff-match-patch | diff patch} + * * The diff patch is used for identifying the selection/highlight location - * + * * @param range text selection range * @returns diff patch */ @@ -304,11 +326,11 @@ const getArticleTextNodes = ( /** * Return the offsets to the selection/highlight - * + * * @param patch {@link generateDiffPatch|diff patch} identifying a selection/highlight location - * @returns + * @returns * - highlightTextStart - The start of highlight, offset from the start of article by characters - * - highlightTextEnd - The end of highlight (non-inclusive), offset from the start of article by characters + * - highlightTextEnd - The end of highlight (non-inclusive), offset from the start of article by characters * - matchingHighlightContent - the matched highlight */ const selectionOffsetsFromPatch = ( diff --git a/packages/web/lib/highlights/highlightHelpers.ts b/packages/web/lib/highlights/highlightHelpers.ts index f000ca207..7136bfed8 100644 --- a/packages/web/lib/highlights/highlightHelpers.ts +++ b/packages/web/lib/highlights/highlightHelpers.ts @@ -14,6 +14,7 @@ export type SelectionAttributes = { export const highlightIdAttribute = 'omnivore-highlight-id' export const highlightNoteIdAttribute = 'omnivore-highlight-note-id' +export const highlightLabelIdAttribute = 'omnivore-highlight-label-id' export function getHighlightElements(highlightId: string): Element[] { return Array.from( @@ -46,3 +47,23 @@ export function noteImage(color: string | undefined): SVGSVGElement { svg.appendChild(path) return svg } + +export const labelsImage = (color: string | undefined): SVGSVGElement => { + const svgURI = 'http://www.w3.org/2000/svg' + const svg = document.createElementNS(svgURI, 'svg') + svg.setAttribute('viewBox', '0 0 14 14') + svg.setAttribute('width', '14') + svg.setAttribute('height', '14') + svg.setAttribute('fill', 'none') + + const path = document.createElementNS(svgURI, 'path') + path.setAttribute( + 'd', + 'M1.75 3.5V6.517C1.75007 6.82639 1.87303 7.12309 2.09183 7.34183L6.58933 11.8393C6.85297 12.1029 7.21052 12.251 7.58333 12.251C7.95615 12.251 8.3137 12.1029 8.57733 11.8393L11.8393 8.57733C12.1029 8.3137 12.251 7.95615 12.251 7.58333C12.251 7.21052 12.1029 6.85297 11.8393 6.58933L7.34183 2.09183C7.12309 1.87303 6.82639 1.75007 6.517 1.75H3.5C3.03587 1.75 2.59075 1.93437 2.26256 2.26256C1.93437 2.59075 1.75 3.03587 1.75 3.5Z' + ) + path.setAttribute('stroke', `rgba(${highlightColorVar(color)}, 0.8)`) + path.setAttribute('stroke-width', '1.8') + path.setAttribute('stroke-linejoin', 'round') + svg.appendChild(path) + return svg +} diff --git a/packages/web/styles/articleInnerStyling.css b/packages/web/styles/articleInnerStyling.css index faf39b314..c38f290a7 100644 --- a/packages/web/styles/articleInnerStyling.css +++ b/packages/web/styles/articleInnerStyling.css @@ -44,23 +44,25 @@ font-family: var(--text-font-family); } -.article-inner-css .highlight_with_note .highlight_note_button { +.article-inner-css .highlight_note_button { display: unset !important; margin: 0px !important; max-width: unset !important; height: unset !important; padding: 0px 8px; cursor: pointer; + margin-left: 5px !important; +} + +.article-inner-css .highlight_label_button { + display: unset !important; + margin: 0px !important; + max-width: unset !important; + height: unset !important; + padding: 0px 8px; + cursor: pointer; + margin-left: 4px !important; } -/* -on smaller screens we display the note icon -@media (max-width: 768px) { - .highlight_with_note.last_element:after { - content: url(/static/icons/highlight-note-icon.svg); - padding: 0 3px; - cursor: pointer; - } -} */ .article-inner-css h1, .article-inner-css h2, From 398d8162485d6de3f4591fb4b35587d3c976874f Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 16 Feb 2024 15:45:38 +0800 Subject: [PATCH 03/23] Start work on shortcuts --- packages/web/components/templates/navMenu/LibraryMenu.tsx | 5 ++--- packages/web/lib/highlights/highlightHelpers.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 69144d824..48bdcbb33 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -400,9 +400,8 @@ function MenuPanel(props: MenuPanelProps): JSX.Element { Date: Mon, 19 Feb 2024 10:57:37 +0800 Subject: [PATCH 04/23] More work on shortcuts --- .../web/components/elements/StyledText.tsx | 13 ++++ .../templates/navMenu/LibraryMenu.tsx | 68 ++++++++++++++++++- .../queries/useGetSubscriptionsQuery.tsx | 2 + 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index c9460941e..22c782ab0 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -37,6 +37,19 @@ const textVariants = { my: '6px', color: '$grayText', }, + settingsSection: { + fontWeight: '600', + fontSize: '17px', + fontFamily: '$inter', + color: '$grayText', + }, + settingsItem: { + fontSize: '13px', + fontFamily: '$display', + color: '$grayText', + marginBlockStart: '0px', + marginBlockEnd: '0px', + }, userNote: { fontSize: '16px', paddingTop: '0px', diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 48bdcbb33..93d0d76d7 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -2,7 +2,7 @@ import { ReactNode, useEffect, useMemo, useRef } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' -import { Circle } from 'phosphor-react' +import { Circle, Gear, GearSix, UserGear } from 'phosphor-react' import { Subscription, SubscriptionType, @@ -24,6 +24,7 @@ import { FollowingIcon } from '../../elements/icons/FollowingIcon' import { HomeIcon } from '../../elements/icons/HomeIcon' import { LibraryIcon } from '../../elements/icons/LibraryIcon' import { HighlightsIcon } from '../../elements/icons/HighlightsIcon' +import { CoverImage } from '../../elements/CoverImage' export const LIBRARY_LEFT_MENU_WIDTH = '275px' @@ -122,9 +123,10 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { - + + {/* - + */} @@ -149,6 +151,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { { ) } +const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { + const shortcuts = [ + { + id: '12asdfasdf', + name: 'Omnivore Blog', + icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'subscription:"Money Talk"', + type: 'newsletter', + }, + ] + + return ( + + + + SHORTCUTS + + + + {shortcuts.map((shortcut) => { + return ( + + + {shortcut.name} + {/* + + */} + + ) + })} + + ) +} + function SavedSearches( props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined } ): JSX.Element { diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx index 50efdebf8..764b732d9 100644 --- a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -16,6 +16,7 @@ export type Subscription = { newsletterEmail?: string url?: string + icon?: string description?: string status: SubscriptionStatus @@ -57,6 +58,7 @@ export function useGetSubscriptionsQuery( type newsletterEmail url + icon description status unsubscribeMailTo From d6514e1c8d3d0e5bd6891d82c1cfe321d34431a7 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 19 Feb 2024 15:16:24 +0800 Subject: [PATCH 05/23] Add missing files --- .../components/elements/icons/DragIcon.tsx | 73 +++++++ .../components/elements/icons/HomeIcon.tsx | 29 +++ packages/web/pages/settings/shortcuts.tsx | 203 ++++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 packages/web/components/elements/icons/DragIcon.tsx create mode 100644 packages/web/components/elements/icons/HomeIcon.tsx create mode 100644 packages/web/pages/settings/shortcuts.tsx diff --git a/packages/web/components/elements/icons/DragIcon.tsx b/packages/web/components/elements/icons/DragIcon.tsx new file mode 100644 index 000000000..f40c0316b --- /dev/null +++ b/packages/web/components/elements/icons/DragIcon.tsx @@ -0,0 +1,73 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class DragIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + + + + + + ) + } +} diff --git a/packages/web/components/elements/icons/HomeIcon.tsx b/packages/web/components/elements/icons/HomeIcon.tsx new file mode 100644 index 000000000..0f9e7ce0d --- /dev/null +++ b/packages/web/components/elements/icons/HomeIcon.tsx @@ -0,0 +1,29 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class HomeIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + ) + } +} diff --git a/packages/web/pages/settings/shortcuts.tsx b/packages/web/pages/settings/shortcuts.tsx new file mode 100644 index 000000000..bbb7ab543 --- /dev/null +++ b/packages/web/pages/settings/shortcuts.tsx @@ -0,0 +1,203 @@ +import { useMemo, useState } from 'react' +import { applyStoredTheme } from '../../lib/themeUpdater' + +import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery' +import { useGetSavedSearchQuery } from '../../lib/networking/queries/useGetSavedSearchQuery' +import { SettingsLayout } from '../../components/templates/SettingsLayout' +import { Toaster } from 'react-hot-toast' +import { + Box, + VStack, + HStack, + SpanBox, +} from '../../components/elements/LayoutPrimitives' +import { LabelChip } from '../../components/elements/LabelChip' +import { Checkbox } from '@radix-ui/react-checkbox' +import { StyledText } from '../../components/elements/StyledText' +import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' +import { DragIcon } from '../../components/elements/icons/DragIcon' +import { CoverImage } from '../../components/elements/CoverImage' + +export default function Shortcuts(): JSX.Element { + applyStoredTheme() + + return ( + + + + + + + + + + + + ) +} + +const AvailableItems = (): JSX.Element => { + const { labels } = useGetLabelsQuery() + const { savedSearches } = useGetSavedSearchQuery() + const { subscriptions } = useGetSubscriptionsQuery() + + console.log('subscriptions:', subscriptions) + + 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]) + return ( + + Saved Searches + {sortedsavedSearches?.map((search) => { + return ( + + {search.name} + + + + + ) + })} + Labels + {sortedLabels.map((label) => { + return ( + + + + + + + ) + })} + Subscriptions + {sortedSubscriptions.map((subscription) => { + return ( + + {subscription.name} + + + + + ) + })} + + ) +} + +type Shortcut = { + name: string + icon: string + filter: string + type: 'search' | 'label' | 'newsletter' | 'feed' +} + +const SelectedItems = (): JSX.Element => { + const shortcuts = [ + { + id: '12asdfasdf', + name: 'Omnivore Blog', + icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'subscription:"Money Talk"', + type: 'newsletter', + }, + ] + + return ( + + Shortcuts + {shortcuts.map((shortcut) => { + return ( + + + {shortcut.name} + + + + + ) + })} + + ) +} From 67fada0c40c19b834db5e8dd254a472cbbc9747a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 19 Feb 2024 15:38:54 +0800 Subject: [PATCH 06/23] Fix create hgihlight internal API call --- packages/web/lib/highlights/createHighlight.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/lib/highlights/createHighlight.ts b/packages/web/lib/highlights/createHighlight.ts index 098a58c35..9b165483b 100644 --- a/packages/web/lib/highlights/createHighlight.ts +++ b/packages/web/lib/highlights/createHighlight.ts @@ -99,6 +99,7 @@ export async function createHighlight( patch, id, annotations.length > 0, + false, input.color ) From 11cc6b1ec5451920475a90e363e3d52a09b77306 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 10:15:57 +0800 Subject: [PATCH 07/23] More work on shortcuts --- .../web/components/elements/LabelChip.tsx | 15 +- .../elements/icons/NewsletterFlairIcon.tsx | 3 - .../templates/article/ArticleContainer.tsx | 1 - .../templates/navMenu/LibraryMenu.tsx | 193 ++++++++++++++++-- .../web/components/tokens/stitches.config.ts | 14 +- packages/web/pages/settings/shortcuts.tsx | 13 +- 6 files changed, 204 insertions(+), 35 deletions(-) diff --git a/packages/web/components/elements/LabelChip.tsx b/packages/web/components/elements/LabelChip.tsx index 73c249664..9aa959897 100644 --- a/packages/web/components/elements/LabelChip.tsx +++ b/packages/web/components/elements/LabelChip.tsx @@ -1,20 +1,13 @@ import { SpanBox, HStack } from './LayoutPrimitives' import { Circle, X } from 'phosphor-react' -import { isDarkTheme } from '../../lib/themeUpdater' type LabelChipProps = { text: string color: string // expected to be a RGB hex color string isSelected?: boolean - useAppAppearance?: boolean } export function LabelChip(props: LabelChipProps): JSX.Element { - const isDark = isDarkTheme() - - const selectedBorder = isDark ? '#FFEA9F' : 'black' - const unSelectedBorder = isDark ? '#2A2A2A' : '#F5F5F5' - return ( diff --git a/packages/web/components/elements/icons/NewsletterFlairIcon.tsx b/packages/web/components/elements/icons/NewsletterFlairIcon.tsx index 47cef57f8..d6d197a11 100644 --- a/packages/web/components/elements/icons/NewsletterFlairIcon.tsx +++ b/packages/web/components/elements/icons/NewsletterFlairIcon.tsx @@ -6,9 +6,6 @@ import React from 'react' export class NewsletterFlairIcon extends React.Component { render() { - const size = (this.props.size || 26).toString() - const color = (this.props.color || '#2A2A2A').toString() - return ( ))} diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 93d0d76d7..11ba928be 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -2,7 +2,14 @@ import { ReactNode, useEffect, useMemo, useRef } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' -import { Circle, Gear, GearSix, UserGear } from 'phosphor-react' +import { + Circle, + DotsThree, + Gear, + GearSix, + MagnifyingGlass, + UserGear, +} from 'phosphor-react' import { Subscription, SubscriptionType, @@ -25,6 +32,11 @@ import { HomeIcon } from '../../elements/icons/HomeIcon' import { LibraryIcon } from '../../elements/icons/LibraryIcon' import { HighlightsIcon } from '../../elements/icons/HighlightsIcon' import { CoverImage } from '../../elements/CoverImage' +import { Shortcut } from '../../../pages/settings/shortcuts' +import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip' +import { NewsletterFlairIcon } from '../../elements/icons/NewsletterFlairIcon' +import { FeedFlairIcon } from '../../elements/icons/FeedFlairIcon' +import { NewsletterIcon } from '../../elements/icons/NewsletterIcon' export const LIBRARY_LEFT_MENU_WIDTH = '275px' @@ -190,21 +202,62 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { } const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { - const shortcuts = [ + const shortcuts: Shortcut[] = [ { id: '12asdfasdf', name: 'Omnivore Blog', icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', filter: 'subscription:"Money Talk"', + type: 'feed', + }, + { + id: 'sdfsdfgdsfg', + name: 'Follow the Money | Arne & Harr', + filter: 'subscription:"Money Talk"', + type: 'feed', + }, + { + id: 'sdfasdfasdfsdfsdfsgasdfg', + name: 'Andrew Kenneson from Center for the Study of Partisanship and Ideology', + // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'in:all label:"Hockey"', type: 'newsletter', }, + { + id: 'sdfasdfasdfsdfsdfsgasdfg', + name: 'Robert的博客', + // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'in:all label:"Hockey"', + type: 'feed', + }, + { + id: 'sdfasdfasdfasdfasf', + name: 'Oldest First', + // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'in:all label:"Hockey"', + type: 'search', + }, + { + id: 'sdfasdfasdfgasdfg', + name: 'Hockey', + // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + filter: 'in:all label:"Hockey"', + type: 'label', + label: { + id: 'sdfsdfsdf', + name: 'Hockey', + color: '#E98B8B', + createdAt: new Date(), + }, + }, ] + const selected = false return ( { alignment="start" distribution="start" > - + { > SHORTCUTS - + {shortcuts.map((shortcut) => { return ( - { + props.applySearchQuery(shortcut.filter) + props.setShowFilterMenu(false) + e.preventDefault() + }} > - - {shortcut.name} - {/* - - */} - + {(shortcut.type == 'feed' || shortcut.type == 'newsletter') && ( + + )} + {shortcut.type == 'search' && ( + + )} + {shortcut.type == 'label' && } + ) })} ) } +type ShortcutItemProps = { + shortcut: Shortcut +} + +const FeedOrNewsletterShortcut = (props: ShortcutItemProps): JSX.Element => { + return ( + + + {props.shortcut.icon ? ( + + ) : props.shortcut.type == 'newsletter' ? ( + + ) : ( + + )} + + {props.shortcut.name} + + ) +} + +const SearchShortcut = (props: ShortcutItemProps): JSX.Element => { + return ( + + + + + {props.shortcut.name} + + ) +} + +const LabelShortcut = (props: ShortcutItemProps): JSX.Element => { + return ( + + + + ) +} + function SavedSearches( props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined } ): JSX.Element { diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index 4f9c41ec9..8369b93cc 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -188,6 +188,12 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = thFormInput: '#EBEBEB', thHomeIcon: '#2A2A2A', + thLabelChipForeground: '#2A2A2A', // : '#2A2A2A' + thLabelChipBackground: '#F5F5F5', + thLabelChipSelectedBorder: 'black', + thLabelChipUnselectedBorder: '#F5F5F5', + thLabelOutlineChipBorder: '#D9D9D9', + thHeaderIconRing: '#D9D9D9', thHeaderIconInner: '#898989', @@ -307,13 +313,19 @@ const darkThemeSpec = { thLibraryMenuPrimary: '#EBEBEB', thLibraryMenuSecondary: '#EBEBEB', thLibraryMenuUnselected: 'white', - thLibrarySelectionColor: '#3D3D3D', + thLibrarySelectionColor: '#6A6968', thLibraryNavigationMenuFooter: '#3D3D3D', thLibraryMenuFooterHover: '#6A6968', searchActiveOutline: '#866D15', thFormInput: '#3D3D3D', thHomeIcon: '#FFFFFF', + thLabelChipForeground: '#EBEBEB', + thLabelChipBackground: '#2A2A2A', + thLabelChipSelectedBorder: '#FFEA9F', + thLabelChipUnselectedBorder: '#2A2A2A', + thLabelOutlineChipBorder: '#6A696850', + thHeaderIconRing: '#3D3D3D', thHeaderIconInner: '#D9D9D9', diff --git a/packages/web/pages/settings/shortcuts.tsx b/packages/web/pages/settings/shortcuts.tsx index bbb7ab543..28158fa8b 100644 --- a/packages/web/pages/settings/shortcuts.tsx +++ b/packages/web/pages/settings/shortcuts.tsx @@ -17,6 +17,7 @@ import { StyledText } from '../../components/elements/StyledText' import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' import { DragIcon } from '../../components/elements/icons/DragIcon' import { CoverImage } from '../../components/elements/CoverImage' +import { Label } from '../../lib/networking/fragments/labelFragment' export default function Shortcuts(): JSX.Element { applyStoredTheme() @@ -150,11 +151,15 @@ const AvailableItems = (): JSX.Element => { ) } -type Shortcut = { - name: string - icon: string - filter: string +export type Shortcut = { type: 'search' | 'label' | 'newsletter' | 'feed' + + id: string + name: string + filter: string + + icon?: string + label?: Label } const SelectedItems = (): JSX.Element => { From 3ec2865f7ca434a3a4d6ee26242bc5b7ead2df9d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 10:26:37 +0800 Subject: [PATCH 08/23] Menu to navigate to shortcuts editor --- .../components/templates/PrimaryDropdown.tsx | 21 +++++++------------ .../templates/navMenu/LibraryMenu.tsx | 21 ++++++++++++++++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/web/components/templates/PrimaryDropdown.tsx b/packages/web/components/templates/PrimaryDropdown.tsx index e6d462c24..98d1a4b5c 100644 --- a/packages/web/components/templates/PrimaryDropdown.tsx +++ b/packages/web/components/templates/PrimaryDropdown.tsx @@ -218,19 +218,14 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element { onSelect={() => headerDropdownActionHandler('navigate-to-rules')} title="Rules" /> - - headerDropdownActionHandler('navigate-to-api')} - title="API Keys" - /> - - headerDropdownActionHandler('navigate-to-integrations') - } - title="Integrations" - /> - - + headerDropdownActionHandler('navigate-to-api')} + title="API Keys" + /> + headerDropdownActionHandler('navigate-to-integrations')} + title="Integrations" + /> window.open('https://docs.omnivore.app', '_blank')} title="Documentation" diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 11ba928be..27119ee2a 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -37,6 +37,9 @@ import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip' import { NewsletterFlairIcon } from '../../elements/icons/NewsletterFlairIcon' import { FeedFlairIcon } from '../../elements/icons/FeedFlairIcon' import { NewsletterIcon } from '../../elements/icons/NewsletterIcon' +import { DropdownMenu } from '@radix-ui/react-dropdown-menu' +import { Dropdown, DropdownOption } from '../../elements/DropdownElements' +import { useRouter } from 'next/router' export const LIBRARY_LEFT_MENU_WIDTH = '275px' @@ -202,6 +205,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { } const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { + const router = useRouter() const shortcuts: Shortcut[] = [ { id: '12asdfasdf', @@ -283,9 +287,20 @@ const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { > SHORTCUTS - + + } + css={{ ml: 'auto' }} + > + { + router.push(`/settings/shortcuts`) + }} + title="Edit shortcuts" + /> + + {shortcuts.map((shortcut) => { return ( From f39a4a24a53142a829ceaa8e8a143899f31d99c8 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 10:44:14 +0800 Subject: [PATCH 09/23] WIP: make reader header scroll on smaller screens --- .../templates/reader/ReaderHeader.tsx | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/web/components/templates/reader/ReaderHeader.tsx b/packages/web/components/templates/reader/ReaderHeader.tsx index 0f6bb88f4..9ee83e7cf 100644 --- a/packages/web/components/templates/reader/ReaderHeader.tsx +++ b/packages/web/components/templates/reader/ReaderHeader.tsx @@ -2,12 +2,38 @@ import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' import { PrimaryDropdown } from '../PrimaryDropdown' import { LogoBox } from '../../elements/LogoBox' -import { ReactNode } from 'react' +import { ReactNode, useEffect, useState } from 'react' import { DEFAULT_HEADER_HEIGHT } from '../homeFeed/HeaderSpacer' import { theme } from '../../tokens/stitches.config' import { ReaderSettingsIcon } from '../../elements/icons/ReaderSettingsIcon' import { CircleUtilityMenuIcon } from '../../elements/icons/CircleUtilityMenuIcon' +function useScrollDirection() { + const [scrollDirection, setScrollDirection] = useState('up') + + useEffect(() => { + let lastScrollY = window.pageYOffset + + const updateScrollDirection = () => { + const scrollY = window.pageYOffset + const direction = scrollY > lastScrollY ? 'down' : 'up' + if ( + direction !== scrollDirection && + (scrollY - lastScrollY > 10 || scrollY - lastScrollY < -10) + ) { + setScrollDirection(direction) + } + lastScrollY = scrollY > 0 ? scrollY : 0 + } + window.addEventListener('scroll', updateScrollDirection) // add event listener + return () => { + window.removeEventListener('scroll', updateScrollDirection) // clean up + } + }, [scrollDirection]) + + return scrollDirection +} + type ReaderHeaderProps = { alwaysDisplayToolbar: boolean hideDisplaySettings: boolean @@ -16,6 +42,8 @@ type ReaderHeaderProps = { } export function ReaderHeader(props: ReaderHeaderProps): JSX.Element { + const scrollDirection = useScrollDirection() + return ( <> Date: Tue, 20 Feb 2024 10:48:42 +0800 Subject: [PATCH 10/23] Add keys for shortcuts --- packages/web/components/templates/navMenu/LibraryMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 27119ee2a..6bff3edde 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -305,6 +305,7 @@ const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { {shortcuts.map((shortcut) => { return ( Date: Tue, 20 Feb 2024 10:57:38 +0800 Subject: [PATCH 11/23] Add missing files --- .../components/elements/OutlinedLabelChip.tsx | 35 +++++++++++++++++++ .../elements/icons/NewsletterIcon.tsx | 32 +++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 packages/web/components/elements/OutlinedLabelChip.tsx create mode 100644 packages/web/components/elements/icons/NewsletterIcon.tsx diff --git a/packages/web/components/elements/OutlinedLabelChip.tsx b/packages/web/components/elements/OutlinedLabelChip.tsx new file mode 100644 index 000000000..5991fd378 --- /dev/null +++ b/packages/web/components/elements/OutlinedLabelChip.tsx @@ -0,0 +1,35 @@ +import { SpanBox, HStack } from './LayoutPrimitives' +import { Circle, X } from 'phosphor-react' + +type LabelChipProps = { + text: string + color: string // expected to be a RGB hex color string + isSelected?: boolean +} + +export function OutlinedLabelChip(props: LabelChipProps): JSX.Element { + return ( + + + {props.text} + + ) +} diff --git a/packages/web/components/elements/icons/NewsletterIcon.tsx b/packages/web/components/elements/icons/NewsletterIcon.tsx new file mode 100644 index 000000000..490576954 --- /dev/null +++ b/packages/web/components/elements/icons/NewsletterIcon.tsx @@ -0,0 +1,32 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class NewsletterIcon extends React.Component { + render() { + const size = (this.props.size || 20).toString() + + return ( + + + + + + + ) + } +} From f28f48f9d2f5d8be380132872cd10d1ce2553ef6 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 11:36:13 +0800 Subject: [PATCH 12/23] Improve the settings menu divider --- packages/web/components/templates/navMenu/SettingsMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/web/components/templates/navMenu/SettingsMenu.tsx b/packages/web/components/templates/navMenu/SettingsMenu.tsx index c674d7282..620fd1857 100644 --- a/packages/web/components/templates/navMenu/SettingsMenu.tsx +++ b/packages/web/components/templates/navMenu/SettingsMenu.tsx @@ -12,8 +12,9 @@ import { NavMenuFooter } from './Footer' const HorizontalDivider = styled(SpanBox, { width: '100%', height: '1px', - my: '25px', - background: `${theme.colors.grayLine.toString()}`, + my: '5px', + opacity: '0.1', + background: `${theme.colors.thTextSubtle3.toString()}`, }) const StyledLink = styled(SpanBox, { From dae0687fd26cd1dcb75f5ac12c0da00a28d4bdb3 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 13:20:08 +0800 Subject: [PATCH 13/23] Allow toggling legacy menu, better header for the left menu popover on mobile --- .../templates/homeFeed/HomeFeedContainer.tsx | 47 +- .../templates/navMenu/LibraryLegacyMenu.tsx | 610 ++++++++++++++++++ .../templates/navMenu/LibraryMenu.tsx | 39 +- .../templates/navMenu/SettingsMenu.tsx | 1 + packages/web/pages/settings/shortcuts.tsx | 58 ++ 5 files changed, 731 insertions(+), 24 deletions(-) create mode 100644 packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 86d08c070..6ef10c1c1 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -37,6 +37,7 @@ import { EditLibraryItemModal } from './EditItemModals' import { EmptyLibrary } from './EmptyLibrary' import { HighlightItemsLayout } from './HighlightsLayout' import { LibraryFilterMenu } from '../navMenu/LibraryMenu' +import { LibraryLegacyMenu } from '../navMenu/LibraryLegacyMenu' import { LibraryHeader, MultiSelectMode } from './LibraryHeader' import { UploadModal } from '../UploadModal' import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation' @@ -85,11 +86,13 @@ export function HomeFeedContainer(): JSX.Element { const gridContainerRef = useRef(null) - const [labelsTarget, setLabelsTarget] = - useState(undefined) + const [labelsTarget, setLabelsTarget] = useState( + undefined + ) - const [notebookTarget, setNotebookTarget] = - useState(undefined) + const [notebookTarget, setNotebookTarget] = useState( + undefined + ) const [showAddLinkModal, setShowAddLinkModal] = useState(false) const [showEditTitleModal, setShowEditTitleModal] = useState(false) @@ -931,6 +934,10 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { key: 'libraryLayout', initialValue: 'LIST_LAYOUT', }) + const [navMenuStyle] = usePersistedState<'legacy' | 'shortcuts'>({ + key: 'library-nav-menu-style', + initialValue: 'shortcuts', + }) const updateLayout = useCallback( async (newLayout: LayoutType) => { @@ -967,16 +974,28 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { )} - { - props.applySearchQuery(searchQuery) - }} - showFilterMenu={showFilterMenu} - setShowFilterMenu={setShowFilterMenu} - /> - + {navMenuStyle == 'shortcuts' && ( + { + props.applySearchQuery(searchQuery) + }} + showFilterMenu={showFilterMenu} + setShowFilterMenu={setShowFilterMenu} + /> + )} + {navMenuStyle == 'legacy' && ( + { + props.applySearchQuery(searchQuery) + }} + showFilterMenu={showFilterMenu} + setShowFilterMenu={setShowFilterMenu} + /> + )} {!props.isValidating && props.mode == 'highlights' && ( void + + searchTerm: string | undefined + applySearchQuery: (searchTerm: string) => void + + showFilterMenu: boolean + setShowFilterMenu: (show: boolean) => void +} + +export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element { + const [labels, setLabels] = usePersistedState({ + key: 'menu-labels', + isSessionStorage: false, + initialValue: [], + }) + const [savedSearches, setSavedSearches] = usePersistedState({ + key: 'menu-searches', + isSessionStorage: false, + initialValue: [], + }) + const [subscriptions, setSubscriptions] = usePersistedState({ + key: 'menu-subscriptions', + isSessionStorage: false, + initialValue: [], + }) + const labelsResponse = useGetLabelsQuery() + const searchesResponse = useGetSavedSearchQuery() + const subscriptionsResponse = useGetSubscriptionsQuery() + + useEffect(() => { + if ( + !labelsResponse.error && + !labelsResponse.isLoading && + labelsResponse.labels + ) { + setLabels(labelsResponse.labels) + } + }, [setLabels, labelsResponse]) + + useEffect(() => { + if ( + !subscriptionsResponse.error && + !subscriptionsResponse.isLoading && + subscriptionsResponse.subscriptions + ) { + setSubscriptions(subscriptionsResponse.subscriptions) + } + }, [setSubscriptions, subscriptionsResponse]) + + useEffect(() => { + if ( + !searchesResponse.error && + !searchesResponse.isLoading && + searchesResponse.savedSearches + ) { + setSavedSearches(searchesResponse.savedSearches) + } + }, [setSavedSearches, searchesResponse]) + + return ( + <> + + + + + + + + + + + {/* This spacer pushes library content to the right of + the fixed left side menu. */} + + + ) +} + +function SavedSearches( + props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined } +): JSX.Element { + const sortedSearches = useMemo(() => { + return props.savedSearches + ?.filter((it) => it.visible) + ?.sort( + (left: SavedSearch, right: SavedSearch) => + left.position - right.position + ) + }, [props.savedSearches]) + + useRegisterActions( + (sortedSearches ?? []).map((item, idx) => { + const key = String(idx + 1) + return { + id: `saved_search_${key}`, + name: item.name, + shortcut: [key], + section: 'Saved Searches', + keywords: '?' + item.name, + perform: () => { + props.applySearchQuery(item.filter) + }, + } + }), + [props.savedSearches] + ) + + const [collapsed, setCollapsed] = usePersistedState({ + key: `--saved-searches-collapsed`, + initialValue: false, + }) + + return ( + + {!collapsed && + sortedSearches && + sortedSearches?.map((item) => ( + + ))} + {!collapsed && sortedSearches !== undefined && ( + + )} + + + + ) +} + +function Subscriptions( + props: LibraryFilterMenuProps & { subscriptions: Subscription[] | undefined } +): JSX.Element { + const [collapsed, setCollapsed] = usePersistedState({ + key: `--subscriptions-collapsed`, + initialValue: false, + }) + + const sortedSubscriptions = useMemo(() => { + if (!props.subscriptions) { + return [] + } + return props.subscriptions + .filter((s) => s.status == 'ACTIVE') + .sort((a, b) => a.name.localeCompare(b.name)) + }, [props.subscriptions]) + + useRegisterActions( + (sortedSubscriptions ?? []).map((subscription, idx) => { + const key = String(idx + 1) + const name = subscription.name + return { + id: `subscription_${key}`, + section: 'Subscriptions', + name: name, + keywords: '*' + name, + perform: () => { + props.applySearchQuery(`subscription:\"${name}\"`) + }, + } + }), + [sortedSubscriptions] + ) + + return ( + + {!collapsed ? ( + <> + + + + {(sortedSubscriptions ?? []).map((item) => { + switch (item.type) { + case SubscriptionType.NEWSLETTER: + return ( + + ) + case SubscriptionType.RSS: + return ( + + ) + } + })} + + + ) : ( + + )} + + ) +} + +function Labels( + props: LibraryFilterMenuProps & { labels: Label[] } +): JSX.Element { + const [collapsed, setCollapsed] = usePersistedState({ + key: `--labels-collapsed`, + initialValue: false, + }) + + const sortedLabels = useMemo(() => { + return props.labels.sort((left: Label, right: Label) => + left.name.localeCompare(right.name) + ) + }, [props.labels]) + + return ( + + {!collapsed && ( + <> + {sortedLabels.map((item) => { + return + })} + + + )} + + ) +} + +type MenuPanelProps = { + title: string + children: ReactNode + editFunc?: () => void + editTitle?: string + hideBottomBorder?: boolean + collapsed: boolean + setCollapsed: (collapsed: boolean) => void +} + +function MenuPanel(props: MenuPanelProps): JSX.Element { + return ( + + + + {props.title} + + + + + + {props.children} + + ) +} + +type FilterButtonProps = { + text: string + + filterTerm: string + searchTerm: string | undefined + + applySearchQuery: (searchTerm: string) => void + + setShowFilterMenu: (show: boolean) => void +} + +function FilterButton(props: FilterButtonProps): JSX.Element { + const isInboxFilter = (filter: string) => { + return filter === '' || filter === 'in:inbox' + } + const selected = useMemo(() => { + if (isInboxFilter(props.filterTerm) && !props.searchTerm) { + return true + } + return props.searchTerm === props.filterTerm + }, [props.searchTerm, props.filterTerm]) + + return ( + { + props.applySearchQuery(props.filterTerm) + props.setShowFilterMenu(false) + e.preventDefault() + }} + > + {props.text} + + ) +} + +type LabelButtonProps = { + label: Label + searchTerm: string | undefined + applySearchQuery: (searchTerm: string) => void +} + +function LabelButton(props: LabelButtonProps): JSX.Element { + const labelId = `checkbox-label-${props.label.id}` + const checkboxRef = useRef(null) + const state = useMemo(() => { + const term = props.searchTerm ?? '' + if (term.indexOf(`label:\"${props.label.name}\"`) >= 0) { + return 'on' + } + return 'off' + }, [props.searchTerm, props.label]) + + return ( + + + + { + if (e.target.checked) { + props.applySearchQuery( + `${props.searchTerm ?? ''} label:\"${props.label.name}\"` + ) + } else { + const query = + props.searchTerm?.replace( + `label:\"${props.label.name}\"`, + '' + ) ?? '' + props.applySearchQuery(query) + } + }} + /> + + + ) +} + +type EditButtonProps = { + title: string + destination: string +} + +function EditButton(props: EditButtonProps): JSX.Element { + return ( + + + {props.title} + + + ) +} diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 6bff3edde..6f52fae54 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -2,14 +2,7 @@ import { ReactNode, useEffect, useMemo, useRef } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' -import { - Circle, - DotsThree, - Gear, - GearSix, - MagnifyingGlass, - UserGear, -} from 'phosphor-react' +import { Circle, DotsThree, MagnifyingGlass, X } from 'phosphor-react' import { Subscription, SubscriptionType, @@ -123,7 +116,7 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { width: '100%', transition: 'visibility 0s, top 150ms', }, - zIndex: 3, + zIndex: 10, }} > - + + + + + diff --git a/packages/web/components/templates/navMenu/SettingsMenu.tsx b/packages/web/components/templates/navMenu/SettingsMenu.tsx index 620fd1857..c9b0bac7a 100644 --- a/packages/web/components/templates/navMenu/SettingsMenu.tsx +++ b/packages/web/components/templates/navMenu/SettingsMenu.tsx @@ -88,6 +88,7 @@ export function SettingsMenu(): JSX.Element { { name: 'Feeds', destination: '/settings/feeds' }, { name: 'Subscriptions', destination: '/settings/subscriptions' }, { name: 'Labels', destination: '/settings/labels' }, + { name: 'Shortcuts', destination: '/settings/shortcuts' }, { name: 'Saved Searches', destination: '/settings/saved-searches' }, { name: 'Pinned Searches', destination: '/settings/pinned-searches' }, ] diff --git a/packages/web/pages/settings/shortcuts.tsx b/packages/web/pages/settings/shortcuts.tsx index 28158fa8b..5caf39fe5 100644 --- a/packages/web/pages/settings/shortcuts.tsx +++ b/packages/web/pages/settings/shortcuts.tsx @@ -18,9 +18,17 @@ import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSub import { DragIcon } from '../../components/elements/icons/DragIcon' import { CoverImage } from '../../components/elements/CoverImage' import { Label } from '../../lib/networking/fragments/labelFragment' +import { usePersistedState } from '../../lib/hooks/usePersistedState' +import { CheckSquare, Square } from 'phosphor-react' export default function Shortcuts(): JSX.Element { applyStoredTheme() + const [navMenuStyle, setNavMenuStyle] = usePersistedState< + 'legacy' | 'shortcuts' + >({ + key: 'library-nav-menu-style', + initialValue: 'shortcuts', + }) return ( @@ -44,6 +52,56 @@ export default function Shortcuts(): JSX.Element { maxWidth: '880px', }} > + + + Shortcuts + + + Use shortcuts to access your most important reads quickly + + + { + // setHidePinnedSearches(!hidePinnedSearches) + setNavMenuStyle( + navMenuStyle == 'shortcuts' ? 'legacy' : 'shortcuts' + ) + event.preventDefault() + }} + > + {navMenuStyle === 'shortcuts' ? ( + + ) : ( + + )} + + Enable shortcuts + + Date: Tue, 20 Feb 2024 13:22:17 +0800 Subject: [PATCH 14/23] Add menu close button to the legacy menu --- .../templates/navMenu/LibraryLegacyMenu.tsx | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx b/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx index 6dc8948e6..e88263331 100644 --- a/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx @@ -2,7 +2,7 @@ import { ReactNode, useEffect, useMemo, useRef } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' -import { Circle } from 'phosphor-react' +import { Circle, X } from 'phosphor-react' import { Subscription, SubscriptionType, @@ -103,19 +103,45 @@ export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element { width: '100%', transition: 'visibility 0s, top 150ms', }, - zIndex: 3, + zIndex: 10, }} > - + + + + + From e649e07e8f3fb31551d8a6195da1c275aba864a5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 13:44:26 +0800 Subject: [PATCH 15/23] Improve left menu toggle --- packages/web/components/templates/homeFeed/LibraryHeader.tsx | 3 ++- packages/web/components/templates/navMenu/LibraryMenu.tsx | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/web/components/templates/homeFeed/LibraryHeader.tsx b/packages/web/components/templates/homeFeed/LibraryHeader.tsx index fc402b755..96aae0ed1 100644 --- a/packages/web/components/templates/homeFeed/LibraryHeader.tsx +++ b/packages/web/components/templates/homeFeed/LibraryHeader.tsx @@ -48,7 +48,8 @@ export const headerControlWidths = ( return { width: '95%', '@mdDown': { - width: multiSelectMode !== 'off' ? '100%' : '95%', + padding: '15px', + width: '100%', }, '@media (min-width: 930px)': { width: '620px', diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 6f52fae54..43fa4121f 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -112,9 +112,10 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { display: 'none', }, '@mdDown': { - visibility: props.showFilterMenu ? 'visible' : 'hidden', width: '100%', - transition: 'visibility 0s, top 150ms', + transition: 'left 50ms, top 750ms', + left: props.showFilterMenu ? '0' : '-500px', + visibility: props.showFilterMenu ? 'visible' : 'hidden', }, zIndex: 10, }} From 42bb460ef4e664819f90e516f346ceac7b701866 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 13:48:30 +0800 Subject: [PATCH 16/23] Improve the menu animation on mobile --- .../web/components/templates/navMenu/LibraryLegacyMenu.tsx | 5 +++-- packages/web/components/templates/navMenu/LibraryMenu.tsx | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx b/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx index e88263331..f9c34c9c2 100644 --- a/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryLegacyMenu.tsx @@ -99,9 +99,10 @@ export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element { display: 'none', }, '@mdDown': { - visibility: props.showFilterMenu ? 'visible' : 'hidden', width: '100%', - transition: 'visibility 0s, top 150ms', + transition: 'top 100ms, visibility 100ms', + top: props.showFilterMenu ? '0' : '100%', + visibility: props.showFilterMenu ? 'visible' : 'hidden', }, zIndex: 10, }} diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 43fa4121f..fa4d47375 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -113,8 +113,8 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element { }, '@mdDown': { width: '100%', - transition: 'left 50ms, top 750ms', - left: props.showFilterMenu ? '0' : '-500px', + transition: 'top 100ms, visibility 100ms', + top: props.showFilterMenu ? '0' : '100%', visibility: props.showFilterMenu ? 'visible' : 'hidden', }, zIndex: 10, From e12d1c64b4263a7ead825b7ca62a738f1974081c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 14:40:16 +0800 Subject: [PATCH 17/23] Remove settings header, make legacy the default menu option --- .../components/patterns/SettingsHeader.tsx | 35 ------------------- .../components/templates/SettingsLayout.tsx | 1 - .../templates/homeFeed/HomeFeedContainer.tsx | 2 +- 3 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 packages/web/components/patterns/SettingsHeader.tsx diff --git a/packages/web/components/patterns/SettingsHeader.tsx b/packages/web/components/patterns/SettingsHeader.tsx deleted file mode 100644 index a7cabd1f5..000000000 --- a/packages/web/components/patterns/SettingsHeader.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { HStack } from '../elements/LayoutPrimitives' -import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' -import { DEFAULT_HEADER_HEIGHT } from '../templates/homeFeed/HeaderSpacer' -import { LogoBox } from '../elements/LogoBox' - -type HeaderProps = { - user?: UserBasicData -} - -export function SettingsHeader(props: HeaderProps): JSX.Element { - return ( - - ) -} diff --git a/packages/web/components/templates/SettingsLayout.tsx b/packages/web/components/templates/SettingsLayout.tsx index 6f05780f7..c5ca2358b 100644 --- a/packages/web/components/templates/SettingsLayout.tsx +++ b/packages/web/components/templates/SettingsLayout.tsx @@ -47,7 +47,6 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element { css={{ width: '100%', height: '100%', minHeight: '100vh' }} > - ({ key: 'library-nav-menu-style', - initialValue: 'shortcuts', + initialValue: 'legacy', }) const updateLayout = useCallback( From 8b012ce29f82909f01b39fb54ce02c3e775344e5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 17:19:11 +0800 Subject: [PATCH 18/23] When items fail to process in time mark the node so we don't repeat too much --- .../web/components/patterns/LibraryCards/LibraryListCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx index b930e9c68..3a1a4de18 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx @@ -2,7 +2,6 @@ import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives' import { LabelChip } from '../../elements/LabelChip' import type { LinkedItemCardProps } from './CardTypes' import { useCallback, useState } from 'react' -import Link from 'next/link' import { AuthorInfoStyle, CardCheckbox, From 3f8f1a8127e93552da942ae358e0598c82e17830 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 17:24:51 +0800 Subject: [PATCH 19/23] Remove settings header --- packages/web/components/templates/SettingsLayout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web/components/templates/SettingsLayout.tsx b/packages/web/components/templates/SettingsLayout.tsx index c5ca2358b..b0b3fab46 100644 --- a/packages/web/components/templates/SettingsLayout.tsx +++ b/packages/web/components/templates/SettingsLayout.tsx @@ -1,6 +1,5 @@ import { Box, HStack, VStack } from '../elements/LayoutPrimitives' import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' -import { SettingsHeader } from '../patterns/SettingsHeader' import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts' import { useRouter } from 'next/router' From 741438aba0b8c5c7f3b32037daf45f6c5384360e Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 17:25:11 +0800 Subject: [PATCH 20/23] Fix issue with failed items continuing to process --- .../templates/homeFeed/HomeFeedContainer.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 9973eb058..b1c994635 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -69,7 +69,7 @@ const debouncedFetchSearchResults = debounce((query, cb) => { // We set a relatively high delay for the refresh at the end, as it's likely there's an issue // in processing. We give it the best attempt to be able to resolve, but if it doesn't we set // the state as Failed. On refresh it will try again if the backend sends "PROCESSING" -const TIMEOUT_DELAYS = [1000, 2000, 2500, 3500, 5000, 10000, 60000] +const TIMEOUT_DELAYS = [2000, 3500, 5000] export function HomeFeedContainer(): JSX.Element { const { viewerData } = useGetViewerQuery() @@ -209,8 +209,19 @@ export function HomeFeedContainer(): JSX.Element { let startIdx = 0 const seeIfUpdated = async () => { - if (startIdx > TIMEOUT_DELAYS.length) { + console.log( + 'checking if updated: ', + startIdx, + TIMEOUT_DELAYS, + TIMEOUT_DELAYS.length + ) + if (startIdx >= TIMEOUT_DELAYS.length) { item.node.state = State.FAILED + const updatedArticle = { ...item } + updatedArticle.node = { ...item.node } + updatedArticle.isLoading = false + console.log(`Updating Metadata of ${item.node.slug}.`) + performActionOnItem('update-item', updatedArticle) return } From f8cffd89f70e913540a3e105e45a722c56055def Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 17:25:25 +0800 Subject: [PATCH 21/23] Remove debug --- .../web/components/templates/homeFeed/HomeFeedContainer.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index b1c994635..f64700bca 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -209,12 +209,6 @@ export function HomeFeedContainer(): JSX.Element { let startIdx = 0 const seeIfUpdated = async () => { - console.log( - 'checking if updated: ', - startIdx, - TIMEOUT_DELAYS, - TIMEOUT_DELAYS.length - ) if (startIdx >= TIMEOUT_DELAYS.length) { item.node.state = State.FAILED const updatedArticle = { ...item } From 1514aa4f4fd87c71b26ac0fb59e987acff4f12de Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 20 Feb 2024 17:25:52 +0800 Subject: [PATCH 22/23] Pull shortcuts from local storage --- .../templates/navMenu/LibraryMenu.tsx | 107 ++++++++++-------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index fa4d47375..e82bda863 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -226,56 +226,62 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { const router = useRouter() - const shortcuts: Shortcut[] = [ - { - id: '12asdfasdf', - name: 'Omnivore Blog', - icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'subscription:"Money Talk"', - type: 'feed', - }, - { - id: 'sdfsdfgdsfg', - name: 'Follow the Money | Arne & Harr', - filter: 'subscription:"Money Talk"', - type: 'feed', - }, - { - id: 'sdfasdfasdfsdfsdfsgasdfg', - name: 'Andrew Kenneson from Center for the Study of Partisanship and Ideology', - // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'in:all label:"Hockey"', - type: 'newsletter', - }, - { - id: 'sdfasdfasdfsdfsdfsgasdfg', - name: 'Robert的博客', - // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'in:all label:"Hockey"', - type: 'feed', - }, - { - id: 'sdfasdfasdfasdfasf', - name: 'Oldest First', - // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'in:all label:"Hockey"', - type: 'search', - }, - { - id: 'sdfasdfasdfgasdfg', - name: 'Hockey', - // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'in:all label:"Hockey"', - type: 'label', - label: { - id: 'sdfsdfsdf', - name: 'Hockey', - color: '#E98B8B', - createdAt: new Date(), - }, - }, - ] - const selected = false + const [shortcuts] = usePersistedState({ + key: 'shortcuts', + isSessionStorage: false, + initialValue: [], + }) + + // const shortcuts: Shortcut[] = [ + // { + // id: '12asdfasdf', + // name: 'Omnivore Blog', + // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + // filter: 'subscription:"Money Talk"', + // type: 'feed', + // }, + // { + // id: 'sdfsdfgdsfg', + // name: 'Follow the Money | Arne & Harr', + // filter: 'subscription:"Money Talk"', + // type: 'feed', + // }, + // { + // id: 'sdfasdfasdfsdfsdfsgasdfg', + // name: 'Andrew Kenneson from Center for the Study of Partisanship and Ideology', + // // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + // filter: 'in:all label:"Hockey"', + // type: 'newsletter', + // }, + // { + // id: 'sdfasdfasdfsdfsdfsgasdfg', + // name: 'Robert的博客', + // // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + // filter: 'in:all label:"Hockey"', + // type: 'feed', + // }, + // { + // id: 'sdfasdfasdfasdfasf', + // name: 'Oldest First', + // // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + // filter: 'in:all label:"Hockey"', + // type: 'search', + // }, + // { + // id: 'sdfasdfasdfgasdfg', + // name: 'Hockey', + // // icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', + // filter: 'in:all label:"Hockey"', + // type: 'label', + // label: { + // id: 'sdfsdfsdf', + // name: 'Hockey', + // color: '#E98B8B', + // createdAt: new Date(), + // }, + // }, + // ] + // return ( { {shortcuts.map((shortcut) => { + const selected = props.searchTerm === shortcut.filter return ( Date: Tue, 20 Feb 2024 18:43:21 +0800 Subject: [PATCH 23/23] More work on editing shortcuts --- .../web/components/elements/StyledText.tsx | 4 + .../templates/navMenu/LibraryMenu.tsx | 2 +- packages/web/pages/settings/shortcuts.tsx | 384 ++++++++++++++++-- 3 files changed, 355 insertions(+), 35 deletions(-) diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index 22c782ab0..50ad458fa 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -42,6 +42,10 @@ const textVariants = { fontSize: '17px', fontFamily: '$inter', color: '$grayText', + m: '0px', + my: '15px', + marginBlockStart: '0px', + marginBlockEnd: '0px', }, settingsItem: { fontSize: '13px', diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index e82bda863..693030058 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -227,7 +227,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => { const router = useRouter() const [shortcuts] = usePersistedState({ - key: 'shortcuts', + key: 'library-shortcuts', isSessionStorage: false, initialValue: [], }) diff --git a/packages/web/pages/settings/shortcuts.tsx b/packages/web/pages/settings/shortcuts.tsx index 5caf39fe5..1ee7d39f0 100644 --- a/packages/web/pages/settings/shortcuts.tsx +++ b/packages/web/pages/settings/shortcuts.tsx @@ -1,4 +1,11 @@ -import { useMemo, useState } from 'react' +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useReducer, + useState, +} from 'react' import { applyStoredTheme } from '../../lib/themeUpdater' import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery' @@ -10,16 +17,27 @@ import { VStack, HStack, SpanBox, + Separator, } from '../../components/elements/LayoutPrimitives' import { LabelChip } from '../../components/elements/LabelChip' -import { Checkbox } from '@radix-ui/react-checkbox' import { StyledText } from '../../components/elements/StyledText' -import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' +import { + Subscription, + SubscriptionType, + useGetSubscriptionsQuery, +} from '../../lib/networking/queries/useGetSubscriptionsQuery' import { DragIcon } from '../../components/elements/icons/DragIcon' import { CoverImage } from '../../components/elements/CoverImage' import { Label } from '../../lib/networking/fragments/labelFragment' import { usePersistedState } from '../../lib/hooks/usePersistedState' import { CheckSquare, Square } from 'phosphor-react' +import { Button } from '../../components/elements/Button' +import { styled } from '@stitches/react' +import { SavedSearch } from '../../lib/networking/fragments/savedSearchFragment' + +type ListAction = 'RESET' | 'ADD_ITEM' | 'REMOVE_ITEM' + +const SHORTCUTS_KEY = 'library-shortcuts' export default function Shortcuts(): JSX.Element { applyStoredTheme() @@ -27,9 +45,80 @@ export default function Shortcuts(): JSX.Element { 'legacy' | 'shortcuts' >({ key: 'library-nav-menu-style', - initialValue: 'shortcuts', + initialValue: 'legacy', }) + const listReducer = ( + state: { state: string; items: Shortcut[] }, + action: { + type: ListAction + item?: Shortcut + } + ) => { + switch (action.type) { + case 'RESET': { + const itemStr = window['localStorage'].getItem(SHORTCUTS_KEY) + if (itemStr) { + try { + const parsed = JSON.parse(itemStr) + if (Array.isArray(parsed)) { + return { state: 'CURRENT', items: parsed as Shortcut[] } + } + } catch (err) { + console.log('error: ', err) + } + } + return { state: 'CURRENT', 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) + return { state: 'CURRENT', items: [...state.items] } + } + case 'REMOVE_ITEM': { + const item = action.item + if (!item) { + return state + } + const updated = state.items.filter((existing) => existing.id != item.id) + return { state: 'CURRENT', items: [...updated] } + } + default: + throw new Error('unknown action') + } + } + + const [shortcuts, dispatchList] = useReducer(listReducer, { + state: 'INITIAL', + items: [], + }) + + useEffect(() => { + try { + if (shortcuts.state == 'CURRENT') { + window['localStorage'].setItem( + SHORTCUTS_KEY, + JSON.stringify(shortcuts.items) + ) + } + } catch (error) { + console.log('error": ', error) + } + }, [shortcuts]) + + useEffect(() => { + dispatchList({ type: 'RESET' }) + }, []) + return ( { - // setHidePinnedSearches(!hidePinnedSearches) setNavMenuStyle( navMenuStyle == 'shortcuts' ? 'legacy' : 'shortcuts' ) @@ -116,8 +204,14 @@ export default function Shortcuts(): JSX.Element { }, }} > - - + + @@ -125,13 +219,22 @@ export default function Shortcuts(): JSX.Element { ) } -const AvailableItems = (): JSX.Element => { +export const SectionSeparator = styled(Separator, { + height: '1px', + my: '30px', + backgroundColor: '$grayBorder', +}) + +type ListProps = { + shortcuts: Shortcut[] + dispatchList: (arg: { type: ListAction; item?: Shortcut | undefined }) => void +} + +const AvailableItems = (props: ListProps): JSX.Element => { const { labels } = useGetLabelsQuery() const { savedSearches } = useGetSavedSearchQuery() const { subscriptions } = useGetSubscriptionsQuery() - console.log('subscriptions:', subscriptions) - const sortedLabels = useMemo(() => { if (!labels) { return [] @@ -158,6 +261,30 @@ const AvailableItems = (): JSX.Element => { a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) ) }, [savedSearches]) + + const searchSelected = useCallback( + (search: SavedSearch) => { + return !!props.shortcuts.find((shortcut) => shortcut.id == search.id) + }, + [props] + ) + + const labelSelected = useCallback( + (label: Label) => { + return !!props.shortcuts.find((shortcut) => shortcut.id == label.id) + }, + [props] + ) + + const subscriptionSelected = useCallback( + (subscription: Subscription) => { + return !!props.shortcuts.find( + (shortcut) => shortcut.id == subscription.id + ) + }, + [props] + ) + return ( { py: '30px', pl: '28px', // becomes labels have some margin built in pr: '30px', - gap: '15px', + gap: '10px', bg: '$thLeftMenuBackground', }} > Saved Searches {sortedsavedSearches?.map((search) => { return ( - + ) })} + + Labels {sortedLabels.map((label) => { return ( - + ) })} + + Subscriptions {sortedSubscriptions.map((subscription) => { return ( - { + const item: Shortcut = { + id: subscription.id, + name: subscription.name, + icon: subscription.icon, + type: + subscription.type == SubscriptionType.NEWSLETTER + ? 'newsletter' + : 'feed', + filter: + subscription.type == SubscriptionType.NEWSLETTER + ? `subscription:\"${subscription.name}\"` + : `rss:\"${subscription.url}\"`, + } + props.dispatchList({ + item, + type: subscriptionSelected(subscription) + ? 'REMOVE_ITEM' + : 'ADD_ITEM', + }) + + event.preventDefault() + }} > {subscription.name} - + {subscriptionSelected(subscription) ? ( + + ) : ( + + )} - + ) })} ) } +type AvailableItemButtonProps = { + shortcut: Shortcut + isSelected: boolean + listAction: (arg: { type: ListAction; item?: Shortcut | undefined }) => void +} + +const AvailableItemButton = (props: AvailableItemButtonProps): JSX.Element => { + const shortcutId = `checkbox-search-${props.shortcut.id}` + return ( + + {props.shortcut.name} + + ) +} + export type Shortcut = { type: 'search' | 'label' | 'newsletter' | 'feed' @@ -220,17 +476,7 @@ export type Shortcut = { label?: Label } -const SelectedItems = (): JSX.Element => { - const shortcuts = [ - { - id: '12asdfasdf', - name: 'Omnivore Blog', - icon: 'https://substackcdn.com/image/fetch/w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F052c15c4-ecfd-4d32-87db-13bcac9afad5_512x512.png', - filter: 'subscription:"Money Talk"', - type: 'newsletter', - }, - ] - +const SelectedItems = (props: ListProps): JSX.Element => { return ( { bg: '$thLeftMenuBackground', }} > - Shortcuts - {shortcuts.map((shortcut) => { + Your shortcuts + {props.shortcuts.map((shortcut) => { return ( { ) } + +type CheckboxButtonProps = { + itemKey: string + title: string + isSelected: boolean + item: Shortcut + + listAction: (arg: { type: ListAction; item?: Shortcut | undefined }) => void + children: ReactNode +} + +function CheckboxButton(props: CheckboxButtonProps): JSX.Element { + const handleChange = useCallback( + (selected: boolean) => { + if (!selected) { + props.listAction({ + type: 'REMOVE_ITEM', + item: props.item, + }) + } else { + props.listAction({ + type: 'ADD_ITEM', + item: props.item, + }) + } + }, + [props] + ) + return ( + { + handleChange(!props.isSelected) + event.preventDefault() + }} + > + {props.isSelected ? ( + + ) : ( + + )} + {props.children} + + ) +}