From f00556f8a671d4ee08a347267d22dda767fae659 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 6 Nov 2023 16:31:36 +0800 Subject: [PATCH 1/9] WIP: add pinned items to the library header --- packages/web/components/elements/Button.tsx | 12 ++-- .../templates/homeFeed/EmptyHighlights.tsx | 3 +- .../templates/homeFeed/HeaderSpacer.tsx | 2 +- .../templates/homeFeed/LibraryHeader.tsx | 65 +++++++++++++++---- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/packages/web/components/elements/Button.tsx b/packages/web/components/elements/Button.tsx index 506fd869b..08459b130 100644 --- a/packages/web/components/elements/Button.tsx +++ b/packages/web/components/elements/Button.tsx @@ -174,14 +174,17 @@ export const Button = styled('button', { }, }, ctaPill: { - borderRadius: '$3', - px: '$3', - py: '$2', + borderRadius: '15px', + px: '10px', + py: '4px', + font: '$inter', + fontSize: '12px', + fontWeight: 'medium', border: '1px solid $grayBorder', bg: '$grayBgActive', '&:hover': { bg: '$grayBgHover', - border: '1px solid $grayBorderHover', + // border: '1px solid $grayBorderHover', }, }, link: { @@ -246,7 +249,6 @@ export const Button = styled('button', { '&:hover': { opacity: 0.5, }, - }, articleActionIcon: { bg: 'transparent', diff --git a/packages/web/components/templates/homeFeed/EmptyHighlights.tsx b/packages/web/components/templates/homeFeed/EmptyHighlights.tsx index 030444c09..bf0985b40 100644 --- a/packages/web/components/templates/homeFeed/EmptyHighlights.tsx +++ b/packages/web/components/templates/homeFeed/EmptyHighlights.tsx @@ -2,6 +2,7 @@ import { Book } from 'phosphor-react' import { VStack } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' +import { HEADER_HEIGHT } from './HeaderSpacer' export function EmptyHighlights(): JSX.Element { return ( @@ -11,7 +12,7 @@ export function EmptyHighlights(): JSX.Element { css={{ color: '$grayTextContrast', textAlign: 'center', - marginTop: '70px', + marginTop: HEADER_HEIGHT, }} > diff --git a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx index 080a5f43c..ef4cd6d13 100644 --- a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx +++ b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx @@ -1,6 +1,6 @@ import { Box } from '../../elements/LayoutPrimitives' -export const HEADER_HEIGHT = '70px' +export const HEADER_HEIGHT = '110px' export function HeaderSpacer(): JSX.Element { return ( diff --git a/packages/web/components/templates/homeFeed/LibraryHeader.tsx b/packages/web/components/templates/homeFeed/LibraryHeader.tsx index d720822c0..63ec91d74 100644 --- a/packages/web/components/templates/homeFeed/LibraryHeader.tsx +++ b/packages/web/components/templates/homeFeed/LibraryHeader.tsx @@ -30,6 +30,7 @@ import { LabelIcon } from '../../elements/icons/LabelIcon' import { ListViewIcon } from '../../elements/icons/ListViewIcon' import { GridViewIcon } from '../../elements/icons/GridViewIcon' import { CaretDownIcon } from '../../elements/icons/CaretDownIcon' +import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon' export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search' @@ -106,19 +107,34 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element { }, }} > - + + + + ) } @@ -400,6 +416,29 @@ export function SearchBox(props: SearchBoxProps): JSX.Element { ) } +const PinnedButtons = (): JSX.Element => { + return ( + + + + + + + + + ) +} + type ControlButtonBoxProps = { layout: LayoutType updateLayout: (layout: LayoutType) => void From 1c3bc51fd0fe3b3cf5933c77c77cd5d590d3ec23 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 6 Nov 2023 17:43:23 +0800 Subject: [PATCH 2/9] Move pinned items into the grid --- packages/web/components/elements/Button.tsx | 16 ++++- .../templates/homeFeed/HeaderSpacer.tsx | 2 +- .../templates/homeFeed/HomeFeedContainer.tsx | 67 +++++++++++++++++++ .../templates/homeFeed/LibraryHeader.tsx | 64 ++++-------------- 4 files changed, 96 insertions(+), 53 deletions(-) diff --git a/packages/web/components/elements/Button.tsx b/packages/web/components/elements/Button.tsx index 08459b130..d48c8cb2c 100644 --- a/packages/web/components/elements/Button.tsx +++ b/packages/web/components/elements/Button.tsx @@ -174,6 +174,7 @@ export const Button = styled('button', { }, }, ctaPill: { + cursor: 'pointer', borderRadius: '15px', px: '10px', py: '4px', @@ -184,7 +185,20 @@ export const Button = styled('button', { bg: '$grayBgActive', '&:hover': { bg: '$grayBgHover', - // border: '1px solid $grayBorderHover', + }, + }, + ctaPillUnselected: { + cursor: 'pointer', + borderRadius: '15px', + px: '10px', + py: '4px', + font: '$inter', + fontSize: '12px', + fontWeight: 'medium', + border: '1px solid transparent', + bg: 'transparent', + '&:hover': { + bg: '$grayBgHover', }, }, link: { diff --git a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx index ef4cd6d13..080a5f43c 100644 --- a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx +++ b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx @@ -1,6 +1,6 @@ import { Box } from '../../elements/LayoutPrimitives' -export const HEADER_HEIGHT = '110px' +export const HEADER_HEIGHT = '70px' export function HeaderSpacer(): JSX.Element { return ( diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 7ca5aa271..c04e55c8c 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -51,6 +51,8 @@ import { NotebookPresenter } from '../article/NotebookPresenter' import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation' import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery' import { searchQuery } from '../../../lib/networking/queries/search' +import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon' +import { theme } from '../../tokens/stitches.config' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' export type LibraryMode = 'reads' | 'highlights' @@ -1019,6 +1021,61 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { ) } +type PinnedItem = { + title: string + search: string +} + +type PinnedButtonsProps = { + items: PinnedItem[] + searchTerm: string | undefined + applySearchQuery: (searchQuery: string) => void +} + +const PinnedButtons = (props: PinnedButtonsProps): JSX.Element => { + if (!props.items.length) { + return <> + } + + return ( + + {props.items.map((item) => { + const style = + item.search == props.searchTerm ? 'ctaPill' : 'ctaPillUnselected' + return ( + + ) + })} + + + ) +} + type LibraryItemsLayoutProps = { layout: LayoutType viewer?: UserBasicData @@ -1057,6 +1114,16 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element { > + + {props.isValidating && props.items.length == 0 && }
{ diff --git a/packages/web/components/templates/homeFeed/LibraryHeader.tsx b/packages/web/components/templates/homeFeed/LibraryHeader.tsx index 63ec91d74..d8d2340ff 100644 --- a/packages/web/components/templates/homeFeed/LibraryHeader.tsx +++ b/packages/web/components/templates/homeFeed/LibraryHeader.tsx @@ -107,34 +107,19 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element { }, }} > - - - - + ) } @@ -416,29 +401,6 @@ export function SearchBox(props: SearchBoxProps): JSX.Element { ) } -const PinnedButtons = (): JSX.Element => { - return ( - - - - - - - - - ) -} - type ControlButtonBoxProps = { layout: LayoutType updateLayout: (layout: LayoutType) => void From 0a1feaa0e06db64b9ccd53b287c4d8d287bf0460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:08:16 +0000 Subject: [PATCH 3/9] Bump firebase-admin from 11.5.0 to 11.11.0 Bumps [firebase-admin](https://github.com/firebase/firebase-admin-node) from 11.5.0 to 11.11.0. - [Release notes](https://github.com/firebase/firebase-admin-node/releases) - [Commits](https://github.com/firebase/firebase-admin-node/compare/v11.5.0...v11.11.0) --- updated-dependencies: - dependency-name: firebase-admin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/yarn.lock b/yarn.lock index 03e3c44c9..aa9ab0cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2367,7 +2367,7 @@ lodash.isundefined "^3.0.1" lodash.uniq "^4.5.0" -"@fastify/busboy@^1.1.0": +"@fastify/busboy@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-1.2.1.tgz#9c6db24a55f8b803b5222753b24fe3aea2ba9ca3" integrity sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q== @@ -2446,7 +2446,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-compat@^0.3.0": +"@firebase/database-compat@^0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-0.3.4.tgz#4e57932f7a5ba761cd5ac946ab6b6ab3f660522c" integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== @@ -2458,7 +2458,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.0": +"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.4": version "0.10.4" resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.10.4.tgz#47ba81113512dab637abace61cfb65f63d645ca7" integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== @@ -2543,15 +2543,15 @@ retry-request "^6.0.0" teeny-request "^9.0.0" -"@google-cloud/firestore@^6.4.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-6.5.0.tgz#5049740e40eca75956264377ca9ffaab2c0f345d" - integrity sha512-U0QwG6pEQxO5c0v0eUylswozmuvlvz7iXSW+I18jzqR2hAFrUq2Weu1wm3NaH8wGD4ZL7W9Be4cMHG5CYU8LuQ== +"@google-cloud/firestore@^6.6.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-6.8.0.tgz#d8c852844c381afaf62592796606c10e178400b5" + integrity sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA== dependencies: fast-deep-equal "^3.1.1" functional-red-black-tree "^1.0.1" google-gax "^3.5.7" - protobufjs "^7.0.0" + protobufjs "^7.2.5" "@google-cloud/functions-framework@3.1.2", "@google-cloud/functions-framework@^3.0.0": version "3.1.2" @@ -2699,10 +2699,10 @@ lodash.snakecase "^4.1.1" p-defer "^3.0.0" -"@google-cloud/storage@^6.5.2": - version "6.9.4" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-6.9.4.tgz#23f6b8b3335e517e0ab3b9c641e64ceac674887e" - integrity sha512-5Li+0xRJ8wgc+vlf7Tgew8COKEJgRzRmC5ozdSYaBj7BK+X39aPPBP6ROsDTiCZ0MpAg7dxIc+HhKiCvQDplXQ== +"@google-cloud/storage@^6.9.5": + version "6.12.0" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-6.12.0.tgz#a5d3093cc075252dca5bd19a3cfda406ad3a9de1" + integrity sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw== dependencies: "@google-cloud/paginator" "^3.0.7" "@google-cloud/projectify" "^3.0.0" @@ -2713,6 +2713,7 @@ duplexify "^4.0.0" ent "^2.2.0" extend "^3.0.2" + fast-xml-parser "^4.2.2" gaxios "^5.0.0" google-auth-library "^8.0.1" mime "^3.0.0" @@ -14508,21 +14509,21 @@ fined@^1.0.1: parse-filepath "^1.0.1" firebase-admin@^11.5.0: - version "11.5.0" - resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-11.5.0.tgz#86aa97e45fd9868be1759dccf04614c488945093" - integrity sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA== + version "11.11.0" + resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-11.11.0.tgz#3d6df5dfbcf85dc1c6c4302f8aee4f7c82171725" + integrity sha512-lp784gXFAJgUEtjSdYNZGTWZqltqjBkoaPSQhDKnmWXJP/MCbWdiDY1hsdkl/6O4O4KFovTjUDLu26sojwdQvw== dependencies: - "@fastify/busboy" "^1.1.0" - "@firebase/database-compat" "^0.3.0" - "@firebase/database-types" "^0.10.0" + "@fastify/busboy" "^1.2.1" + "@firebase/database-compat" "^0.3.4" + "@firebase/database-types" "^0.10.4" "@types/node" ">=12.12.47" jsonwebtoken "^9.0.0" jwks-rsa "^3.0.1" node-forge "^1.3.1" uuid "^9.0.0" optionalDependencies: - "@google-cloud/firestore" "^6.4.0" - "@google-cloud/storage" "^6.5.2" + "@google-cloud/firestore" "^6.6.0" + "@google-cloud/storage" "^6.9.5" flagged-respawn@^1.0.0: version "1.0.1" @@ -23100,7 +23101,7 @@ protobufjs@^6.11.3: "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@^7.0.0: +protobufjs@^7.0.0, protobufjs@^7.2.5: version "7.2.5" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== From 6554eadef2c0df2a39653b1c474b7280690d15e5 Mon Sep 17 00:00:00 2001 From: Remy Chantenay Date: Mon, 6 Nov 2023 11:25:23 +0100 Subject: [PATCH 4/9] Android: Fix Add Link sheet state Signed-off-by: Remy Chantenay --- .../app/omnivore/omnivore/ui/library/LibraryView.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index d5e6f1b5b..35045b459 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -33,6 +33,7 @@ import app.omnivore.omnivore.ui.components.LabelsViewModel import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import app.omnivore.omnivore.ui.reader.PDFReaderActivity import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity +import app.omnivore.omnivore.ui.save.SaveState import app.omnivore.omnivore.ui.save.SaveViewModel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -156,8 +157,14 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe BottomSheetUI { AddLinkSheetContent( saveViewModel = saveViewModel, - onCancel = { libraryViewModel.showAddLinkSheetLiveData.value = false }, - onLinkAdded = { libraryViewModel.showAddLinkSheetLiveData.value = false } + onCancel = { + libraryViewModel.showAddLinkSheetLiveData.value = false + saveViewModel.saveState.value = SaveState.NONE + }, + onLinkAdded = { + libraryViewModel.showAddLinkSheetLiveData.value = false + saveViewModel.saveState.value = SaveState.NONE + } ) } } From fa92e073fc648877465768732578fcbb3d88ec4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:07:19 +0000 Subject: [PATCH 5/9] Bump dayjs from 1.11.7 to 1.11.10 Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.7 to 1.11.10. - [Release notes](https://github.com/iamkun/dayjs/releases) - [Changelog](https://github.com/iamkun/dayjs/blob/dev/CHANGELOG.md) - [Commits](https://github.com/iamkun/dayjs/compare/v1.11.7...v1.11.10) --- updated-dependencies: - dependency-name: dayjs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index aa9ab0cc3..99336ee82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12128,15 +12128,10 @@ dateformat@^3.0.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@1.x, dayjs@^1.10.4: - version "1.11.6" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" - integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== - -dayjs@^1.11.7: - version "1.11.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" - integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== +dayjs@1.x, dayjs@^1.10.4, dayjs@^1.11.7: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== debounce@^1.2.0: version "1.2.0" From 045e7c742d2efa568c076cce3dd801997dcbbf79 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 7 Nov 2023 10:56:23 +0800 Subject: [PATCH 6/9] Force PDF read position on Android, bump version --- android/Omnivore/app/build.gradle | 4 +- .../app/src/main/graphql/schema.graphqls | 122 ++++++++++++------ .../dataService/SyncOfflineChanges.kt | 1 + .../networking/ReadingProgressMutations.kt | 7 +- .../omnivore/ui/reader/PDFReaderViewModel.kt | 19 ++- 5 files changed, 101 insertions(+), 52 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 1578a16ba..df4cac0c6 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 124 - versionName "0.0.124" + versionCode 126 + versionName "0.0.126" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls index 988946951..335c93409 100644 --- a/android/Omnivore/app/src/main/graphql/schema.graphqls +++ b/android/Omnivore/app/src/main/graphql/schema.graphqls @@ -100,7 +100,7 @@ type Article { title: String! unsubHttpUrl: String unsubMailTo: String - updatedAt: Date! + updatedAt: Date uploadFileId: ID url: String! wordsCount: Int @@ -134,7 +134,7 @@ type ArticleSavingRequest { id: ID! slug: String! status: ArticleSavingRequestStatus! - updatedAt: Date! + updatedAt: Date url: String! user: User! userId: ID! @deprecated(reason: "userId has been replaced with user") @@ -638,7 +638,7 @@ type Feature { id: ID! name: String! token: String! - updatedAt: Date! + updatedAt: Date } type FeedArticle { @@ -677,12 +677,14 @@ type FeedArticlesSuccess { type Filter { category: String! createdAt: Date! + defaultFilter: Boolean description: String filter: String! id: ID! name: String! position: Int! - updatedAt: Date! + updatedAt: Date + visible: Boolean } type FiltersError { @@ -823,7 +825,7 @@ type Highlight { shortId: String! suffix: String type: HighlightType! - updatedAt: Date! + updatedAt: Date user: User! } @@ -832,7 +834,7 @@ type HighlightReply { highlight: Highlight! id: ID! text: String! - updatedAt: Date! + updatedAt: Date user: User! } @@ -861,6 +863,13 @@ type ImportFromIntegrationSuccess { success: Boolean! } +enum ImportItemState { + ALL + ARCHIVED + UNARCHIVED + UNREAD +} + type Integration { createdAt: Date! enabled: Boolean! @@ -869,7 +878,7 @@ type Integration { taskName: String token: String! type: IntegrationType! - updatedAt: Date! + updatedAt: Date } enum IntegrationType { @@ -962,7 +971,7 @@ type Link { shareInfo: LinkShareInfo! shareStats: ShareStats! slug: String! - updatedAt: Date! + updatedAt: Date url: String! } @@ -1105,20 +1114,14 @@ type Mutation { createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult! createGroup(input: CreateGroupInput!): CreateGroupResult! createHighlight(input: CreateHighlightInput!): CreateHighlightResult! - createHighlightReply(input: CreateHighlightReplyInput!): CreateHighlightReplyResult! createLabel(input: CreateLabelInput!): CreateLabelResult! createNewsletterEmail: CreateNewsletterEmailResult! - createReaction(input: CreateReactionInput!): CreateReactionResult! - createReminder(input: CreateReminderInput!): CreateReminderResult! deleteAccount(userID: ID!): DeleteAccountResult! deleteFilter(id: ID!): DeleteFilterResult! deleteHighlight(highlightId: ID!): DeleteHighlightResult! - deleteHighlightReply(highlightReplyId: ID!): DeleteHighlightReplyResult! deleteIntegration(id: ID!): DeleteIntegrationResult! deleteLabel(id: ID!): DeleteLabelResult! deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult! - deleteReaction(id: ID!): DeleteReactionResult! - deleteReminder(id: ID!): DeleteReminderResult! deleteRule(id: ID!): DeleteRuleResult! deleteWebhook(id: ID!): DeleteWebhookResult! generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult! @@ -1145,25 +1148,20 @@ type Mutation { setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult! setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult! setFavoriteArticle(id: ID!): SetFavoriteArticleResult! - setFollow(input: SetFollowInput!): SetFollowResult! setIntegration(input: SetIntegrationInput!): SetIntegrationResult! setLabels(input: SetLabelsInput!): SetLabelsResult! setLabelsForHighlight(input: SetLabelsForHighlightInput!): SetLabelsResult! setLinkArchived(input: ArchiveLinkInput!): ArchiveLinkResult! setRule(input: SetRuleInput!): SetRuleResult! - setShareArticle(input: SetShareArticleInput!): SetShareArticleResult! - setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult! setUserPersonalization(input: SetUserPersonalizationInput!): SetUserPersonalizationResult! setWebhook(input: SetWebhookInput!): SetWebhookResult! subscribe(input: SubscribeInput!): SubscribeResult! unsubscribe(name: String!, subscriptionId: ID): UnsubscribeResult! + updateEmail(input: UpdateEmailInput!): UpdateEmailResult! + updateFilter(input: UpdateFilterInput!): UpdateFilterResult! updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult! - updateHighlightReply(input: UpdateHighlightReplyInput!): UpdateHighlightReplyResult! updateLabel(input: UpdateLabelInput!): UpdateLabelResult! - updateLinkShareInfo(input: UpdateLinkShareInfoInput!): UpdateLinkShareInfoResult! updatePage(input: UpdatePageInput!): UpdatePageResult! - updateReminder(input: UpdateReminderInput!): UpdateReminderResult! - updateSharedComment(input: UpdateSharedCommentInput!): UpdateSharedCommentResult! updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionResult! updateUser(input: UpdateUserInput!): UpdateUserResult! updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResult! @@ -1226,7 +1224,7 @@ type Page { readableHtml: String! title: String! type: PageType! - updatedAt: Date! + updatedAt: Date url: String! } @@ -1293,12 +1291,8 @@ type Query { apiKeys: ApiKeysResult! article(format: String, slug: String!, username: String!): ArticleResult! articleSavingRequest(id: ID, url: String): ArticleSavingRequestResult! - articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult! deviceTokens: DeviceTokensResult! - feedArticles(after: String, first: Int, sharedByUser: ID, sort: SortParams): FeedArticlesResult! filters: FiltersResult! - getFollowers(userId: ID): GetFollowersResult! - getFollowing(userId: ID): GetFollowingResult! getUserPersonalization: GetUserPersonalizationResult! groups: GroupsResult! hello: String @@ -1308,11 +1302,9 @@ type Query { newsletterEmails: NewsletterEmailsResult! recentEmails: RecentEmailsResult! recentSearches: RecentSearchesResult! - reminder(linkId: ID!): ReminderResult! rules(enabled: Boolean): RulesResult! search(after: String, first: Int, format: String, includeContent: Boolean, query: String): SearchResult! sendInstallInstructions: SendInstallInstructionsResult! - sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult! subscriptions(sort: SortParams, type: SubscriptionType): SubscriptionsResult! typeaheadSearch(first: Int, query: String!): TypeaheadSearchResult! updatesSince(after: String, first: Int, since: Date!, sort: SortParams): UpdatesSinceResult! @@ -1459,7 +1451,7 @@ type RecommendationGroup { members: [User!]! name: String! topics: [String!] - updatedAt: Date! + updatedAt: Date } type RecommendingUser { @@ -1535,7 +1527,7 @@ type Rule { filter: String! id: ID! name: String! - updatedAt: Date! + updatedAt: Date } type RuleAction { @@ -1586,8 +1578,9 @@ enum SaveArticleReadingProgressErrorCode { } input SaveArticleReadingProgressInput { + force: Boolean id: ID! - readingProgressAnchorIndex: Int! + readingProgressAnchorIndex: Int readingProgressPercent: Float! readingProgressTopPercent: Float } @@ -1629,11 +1622,11 @@ enum SaveFilterErrorCode { } input SaveFilterInput { - category: String! + category: String description: String filter: String! - id: ID name: String! + position: Int } union SaveFilterResult = SaveFilterError | SaveFilterSuccess @@ -1810,7 +1803,7 @@ enum SetFavoriteArticleErrorCode { union SetFavoriteArticleResult = SetFavoriteArticleError | SetFavoriteArticleSuccess type SetFavoriteArticleSuccess { - favoriteArticle: Article! + success: Boolean! } type SetFollowError { @@ -1848,7 +1841,9 @@ enum SetIntegrationErrorCode { input SetIntegrationInput { enabled: Boolean! id: ID + importItemState: ImportItemState name: String! + syncedAt: Date token: String! type: IntegrationType } @@ -2073,9 +2068,8 @@ enum SubscribeErrorCode { } input SubscribeInput { - name: String subscriptionType: SubscriptionType - url: String + url: String! } union SubscribeResult = SubscribeError | SubscribeSuccess @@ -2097,7 +2091,7 @@ type Subscription { type: SubscriptionType! unsubscribeHttpUrl: String unsubscribeMailTo: String - updatedAt: Date! + updatedAt: Date url: String } @@ -2174,6 +2168,53 @@ type UnsubscribeSuccess { subscription: Subscription! } +type UpdateEmailError { + errorCodes: [UpdateEmailErrorCode!]! +} + +enum UpdateEmailErrorCode { + BAD_REQUEST + EMAIL_ALREADY_EXISTS + UNAUTHORIZED +} + +input UpdateEmailInput { + email: String! +} + +union UpdateEmailResult = UpdateEmailError | UpdateEmailSuccess + +type UpdateEmailSuccess { + email: String! + verificationEmailSent: Boolean +} + +type UpdateFilterError { + errorCodes: [UpdateFilterErrorCode!]! +} + +enum UpdateFilterErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +input UpdateFilterInput { + category: String + description: String + filter: String + id: String! + name: String + position: Int + visible: Boolean +} + +union UpdateFilterResult = UpdateFilterError | UpdateFilterSuccess + +type UpdateFilterSuccess { + filter: Filter! +} + type UpdateHighlightError { errorCodes: [UpdateHighlightErrorCode!]! } @@ -2359,7 +2400,9 @@ input UpdateSubscriptionInput { description: String id: ID! lastFetchedAt: Date + lastFetchedChecksum: String name: String + scheduledAt: Date status: SubscriptionStatus } @@ -2485,9 +2528,11 @@ enum UploadImportFileType { } type User { + email: String followersCount: Int friendsCount: Int id: ID! + intercomHash: String isFriend: Boolean @deprecated(reason: "isFriend has been replaced with viewerIsFollowing") isFullUser: Boolean name: String! @@ -2497,6 +2542,7 @@ type User { sharedArticlesCount: Int sharedHighlightsCount: Int sharedNotesCount: Int + source: String viewerIsFollowing: Boolean } @@ -2551,7 +2597,7 @@ type Webhook { eventTypes: [WebhookEvent!]! id: ID! method: String! - updatedAt: Date! + updatedAt: Date url: String! } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt index b1c949474..6110b91e4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt @@ -66,6 +66,7 @@ private suspend fun DataService.syncSavedItem(item: SavedItem) { val isReadingProgressSynced = networker.updateReadingProgress( ReadingProgressParams( id = item.savedItemId, + force = item.contentReader == "PDF", readingProgressPercent = item.readingProgress, readingProgressAnchorIndex = item.readingProgressAnchor ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt index 2fb86e691..2ca820474 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt @@ -5,17 +5,20 @@ import app.omnivore.omnivore.graphql.generated.type.SaveArticleReadingProgressIn import android.util.Log +import com.apollographql.apollo3.api.Optional import com.google.gson.Gson data class ReadingProgressParams( val id: String?, val readingProgressPercent: Double?, - val readingProgressAnchorIndex: Int? + val readingProgressAnchorIndex: Int?, + val force: Boolean? ) { fun asSaveReadingProgressInput() = SaveArticleReadingProgressInput( id = id ?: "", + force = Optional.presentIfNotNull(force), readingProgressPercent = readingProgressPercent ?: 0.0, - readingProgressAnchorIndex = readingProgressAnchorIndex ?: 0 + readingProgressAnchorIndex = Optional.presentIfNotNull(readingProgressAnchorIndex ?: 0) ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt index e674b89ac..4410e077e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt @@ -131,16 +131,15 @@ class PDFReaderViewModel @Inject constructor( fun syncPageChange(currentPageIndex: Int, totalPages: Int) { val rawProgress = ((currentPageIndex + 1).toDouble() / totalPages.toDouble()) * 100 val percent = min(100.0, max(0.0, rawProgress)) - if (percent > currentReadingProgress) { - currentReadingProgress = percent - viewModelScope.launch { - val params = ReadingProgressParams( - id = pdfReaderParamsLiveData.value?.item?.savedItemId, - readingProgressPercent = percent, - readingProgressAnchorIndex = currentPageIndex - ) - networker.updateReadingProgress(params) - } + currentReadingProgress = percent + viewModelScope.launch { + val params = ReadingProgressParams( + id = pdfReaderParamsLiveData.value?.item?.savedItemId, + readingProgressPercent = percent, + readingProgressAnchorIndex = currentPageIndex, + force = true + ) + networker.updateReadingProgress(params) } } From 09c30fe645b8f1c46685d9e1f0f6a6c675c3faca Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 8 Nov 2023 11:27:21 +0800 Subject: [PATCH 7/9] Add pinned searches --- packages/web/components/elements/Button.tsx | 45 +- .../components/patterns/SettingsHeader.tsx | 4 +- .../components/templates/PrimaryDropdown.tsx | 2 +- .../components/templates/SettingsLayout.tsx | 6 +- .../web/components/templates/SettingsMenu.tsx | 4 +- .../templates/article/EpubContainer.tsx | 4 +- .../templates/article/PdfArticleContainer.tsx | 4 +- .../templates/homeFeed/EmptyHighlights.tsx | 5 +- .../templates/homeFeed/HeaderSpacer.tsx | 25 +- .../templates/homeFeed/HighlightsLayout.tsx | 15 +- .../templates/homeFeed/HomeFeedContainer.tsx | 65 --- .../templates/homeFeed/LibraryHeader.tsx | 103 +++-- .../templates/homeFeed/PinnedButtons.tsx | 105 +++++ .../templates/reader/ReaderHeader.tsx | 6 +- .../web/pages/settings/pinned-searches.tsx | 409 ++++++++++++++++++ 15 files changed, 651 insertions(+), 151 deletions(-) create mode 100644 packages/web/components/templates/homeFeed/PinnedButtons.tsx create mode 100644 packages/web/pages/settings/pinned-searches.tsx diff --git a/packages/web/components/elements/Button.tsx b/packages/web/components/elements/Button.tsx index d48c8cb2c..eea66f75a 100644 --- a/packages/web/components/elements/Button.tsx +++ b/packages/web/components/elements/Button.tsx @@ -176,29 +176,34 @@ export const Button = styled('button', { ctaPill: { cursor: 'pointer', borderRadius: '15px', - px: '10px', - py: '4px', + px: '12px', + py: '5px', font: '$inter', fontSize: '12px', - fontWeight: 'medium', - border: '1px solid $grayBorder', - bg: '$grayBgActive', + fontWeight: '700', + whiteSpace: 'nowrap', + color: '$thLibraryMenuPrimary', + border: '1px solid $thLeftMenuBackground', + backgroundColor: '$thLeftMenuBackground', '&:hover': { - bg: '$grayBgHover', + bg: '$thBackgroundActive', + border: '1px solid $thBackgroundActive', }, }, ctaPillUnselected: { cursor: 'pointer', borderRadius: '15px', - px: '10px', - py: '4px', + px: '12px', + py: '5px', font: '$inter', fontSize: '12px', fontWeight: 'medium', - border: '1px solid transparent', - bg: 'transparent', + whiteSpace: 'nowrap', + border: '1px solid $thBackground4', + backgroundColor: '$thBackground4', '&:hover': { - bg: '$grayBgHover', + bg: '$thBackgroundActive', + border: '1px solid $thBackgroundActive', }, }, link: { @@ -210,24 +215,6 @@ export const Button = styled('button', { color: '$thLibraryMenuUnselected', cursor: 'pointer', }, - circularIcon: { - mx: '$1', - display: 'flex', - alignItems: 'center', - fontWeight: 500, - height: 44, - width: 44, - borderRadius: '50%', - justifyContent: 'center', - textAlign: 'center', - background: '$grayBase', - cursor: 'pointer', - border: 'none', - opacity: 0.9, - '&:hover': { - opacity: 1, - }, - }, squareIcon: { mx: '$1', display: 'flex', diff --git a/packages/web/components/patterns/SettingsHeader.tsx b/packages/web/components/patterns/SettingsHeader.tsx index e359126ec..b8c218a08 100644 --- a/packages/web/components/patterns/SettingsHeader.tsx +++ b/packages/web/components/patterns/SettingsHeader.tsx @@ -2,7 +2,7 @@ import { Box, HStack } from '../elements/LayoutPrimitives' import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo' import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' import { PrimaryDropdown } from '../templates/PrimaryDropdown' -import { HEADER_HEIGHT } from '../templates/homeFeed/HeaderSpacer' +import { DEFAULT_HEADER_HEIGHT } from '../templates/homeFeed/HeaderSpacer' import { LogoBox } from '../elements/LogoBox' type HeaderProps = { @@ -23,7 +23,7 @@ export function SettingsHeader(props: HeaderProps): JSX.Element { position: 'fixed', width: '100%', pr: '25px', - height: HEADER_HEIGHT, + height: DEFAULT_HEADER_HEIGHT, '@mdDown': { pr: '15px', }, diff --git a/packages/web/components/templates/PrimaryDropdown.tsx b/packages/web/components/templates/PrimaryDropdown.tsx index 5d46f9932..f28aaa415 100644 --- a/packages/web/components/templates/PrimaryDropdown.tsx +++ b/packages/web/components/templates/PrimaryDropdown.tsx @@ -206,7 +206,7 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element { ) } -const StyledToggleButton = styled('button', { +export const StyledToggleButton = styled('button', { display: 'flex', alignItems: 'center', justifyContent: 'center', diff --git a/packages/web/components/templates/SettingsLayout.tsx b/packages/web/components/templates/SettingsLayout.tsx index f10457ce1..a7df5dedd 100644 --- a/packages/web/components/templates/SettingsLayout.tsx +++ b/packages/web/components/templates/SettingsLayout.tsx @@ -5,13 +5,11 @@ import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortc import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts' import { useRouter } from 'next/router' import { applyStoredTheme } from '../../lib/themeUpdater' -import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' import { useCallback, useEffect, useState } from 'react' import { ConfirmationModal } from '../patterns/ConfirmationModal' import { KeyboardShortcutListModal } from './KeyboardShortcutListModal' import { PageMetaData } from '../patterns/PageMetaData' -import { HEADER_HEIGHT } from './homeFeed/HeaderSpacer' -import { deinitAnalytics } from '../../lib/analytics' +import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer' import { logout } from '../../lib/logout' import { SettingsMenu } from './SettingsMenu' @@ -53,7 +51,7 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element { diff --git a/packages/web/components/templates/SettingsMenu.tsx b/packages/web/components/templates/SettingsMenu.tsx index cfe9d60c1..5663ae6d6 100644 --- a/packages/web/components/templates/SettingsMenu.tsx +++ b/packages/web/components/templates/SettingsMenu.tsx @@ -86,6 +86,8 @@ export function SettingsMenu(): JSX.Element { { name: 'Feeds', destination: '/settings/feeds' }, { name: 'Subscriptions', destination: '/settings/subscriptions' }, { name: 'Labels', destination: '/settings/labels' }, + { name: 'Saved Searches', destination: '/settings/saved-searches' }, + { name: 'Pinned Searches', destination: '/settings/pinned-searches' }, ] const section2 = [ @@ -249,5 +251,5 @@ function SettingsButton(props: SettingsButtonProps): JSX.Element { {props.name} - ); + ) } diff --git a/packages/web/components/templates/article/EpubContainer.tsx b/packages/web/components/templates/article/EpubContainer.tsx index 85a21ad84..c4c25623d 100644 --- a/packages/web/components/templates/article/EpubContainer.tsx +++ b/packages/web/components/templates/article/EpubContainer.tsx @@ -16,7 +16,7 @@ import { pspdfKitKey } from '../../../lib/appConfig' import { NotebookModal } from './NotebookModal' import { HighlightNoteModal } from './HighlightNoteModal' import { showErrorToast } from '../../../lib/toastHelpers' -import { HEADER_HEIGHT } from '../homeFeed/HeaderSpacer' +import { DEFAULT_HEADER_HEIGHT } from '../homeFeed/HeaderSpacer' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' import Epub, { EpubCFI } from 'epubjs' import { Rendition, Contents } from 'epubjs/types' @@ -294,7 +294,7 @@ export default function EpubContainer(props: EpubContainerProps): JSX.Element { paddingBottom: '0px', }, width: '100%', - height: `calc(100vh - ${HEADER_HEIGHT})`, + height: `calc(100vh - ${DEFAULT_HEADER_HEIGHT})`, }} >
diff --git a/packages/web/components/templates/homeFeed/EmptyHighlights.tsx b/packages/web/components/templates/homeFeed/EmptyHighlights.tsx index bf0985b40..1972ee1be 100644 --- a/packages/web/components/templates/homeFeed/EmptyHighlights.tsx +++ b/packages/web/components/templates/homeFeed/EmptyHighlights.tsx @@ -2,9 +2,10 @@ import { Book } from 'phosphor-react' import { VStack } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' -import { HEADER_HEIGHT } from './HeaderSpacer' +import { useGetHeaderHeight } from './HeaderSpacer' export function EmptyHighlights(): JSX.Element { + const headerHeight = useGetHeaderHeight() return ( diff --git a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx index 080a5f43c..43671f53e 100644 --- a/packages/web/components/templates/homeFeed/HeaderSpacer.tsx +++ b/packages/web/components/templates/homeFeed/HeaderSpacer.tsx @@ -1,12 +1,33 @@ +import { usePersistedState } from '../../../lib/hooks/usePersistedState' +import { PinnedSearch } from '../../../pages/settings/pinned-searches' import { Box } from '../../elements/LayoutPrimitives' -export const HEADER_HEIGHT = '70px' +export const DEFAULT_HEADER_HEIGHT = '70px' + +export const useGetHeaderHeight = () => { + const [hidePinnedSearches] = usePersistedState({ + key: '--library-hide-pinned-searches', + initialValue: false, + isSessionStorage: false, + }) + const [pinnedSearches] = usePersistedState({ + key: `--library-pinned-searches`, + initialValue: [], + isSessionStorage: false, + }) + + if (hidePinnedSearches || !pinnedSearches?.length) { + return '70px' + } + return '100px' +} export function HeaderSpacer(): JSX.Element { + const headerHeight = useGetHeaderHeight() return ( diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 75ffcf5d0..12a16849f 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -13,7 +13,6 @@ import { Dropdown, DropdownOption } from '../../elements/DropdownElements' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { MenuTrigger } from '../../elements/MenuTrigger' -import { StyledText } from '../../elements/StyledText' import { MetaStyle, timeAgo, @@ -21,7 +20,7 @@ import { import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard' import { NotebookContent } from '../article/Notebook' import { EmptyHighlights } from './EmptyHighlights' -import { HEADER_HEIGHT } from './HeaderSpacer' +import { useGetHeaderHeight } from './HeaderSpacer' import { highlightsAsMarkdown } from './HighlightItem' type HighlightItemsLayoutProps = { @@ -34,8 +33,10 @@ type HighlightItemsLayoutProps = { export function HighlightItemsLayout( props: HighlightItemsLayoutProps ): JSX.Element { - const [currentItem, setCurrentItem] = - useState(undefined) + const headerHeight = useGetHeaderHeight() + const [currentItem, setCurrentItem] = useState( + undefined + ) const listReducer = ( state: LibraryItem[], @@ -105,7 +106,7 @@ export function HighlightItemsLayout( @@ -118,7 +119,7 @@ export function HighlightItemsLayout( void -} - -const PinnedButtons = (props: PinnedButtonsProps): JSX.Element => { - if (!props.items.length) { - return <> - } - - return ( - - {props.items.map((item) => { - const style = - item.search == props.searchTerm ? 'ctaPill' : 'ctaPillUnselected' - return ( - - ) - })} - - - ) -} - type LibraryItemsLayoutProps = { layout: LayoutType viewer?: UserBasicData @@ -1114,16 +1059,6 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element { > - - {props.isValidating && props.items.length == 0 && }
{ diff --git a/packages/web/components/templates/homeFeed/LibraryHeader.tsx b/packages/web/components/templates/homeFeed/LibraryHeader.tsx index d8d2340ff..43c7bb072 100644 --- a/packages/web/components/templates/homeFeed/LibraryHeader.tsx +++ b/packages/web/components/templates/homeFeed/LibraryHeader.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { theme } from '../../tokens/stitches.config' import { FormInput } from '../../elements/FormElements' @@ -16,7 +16,7 @@ import { import { LayoutType } from './HomeFeedContainer' import { PrimaryDropdown } from '../PrimaryDropdown' import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo' -import { HeaderSpacer, HEADER_HEIGHT } from './HeaderSpacer' +import { HeaderSpacer, useGetHeaderHeight } from './HeaderSpacer' import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu' import { CardCheckbox } from '../../patterns/LibraryCards/LibraryCardStyles' import { Dropdown, DropdownOption } from '../../elements/DropdownElements' @@ -30,7 +30,9 @@ import { LabelIcon } from '../../elements/icons/LabelIcon' import { ListViewIcon } from '../../elements/icons/ListViewIcon' import { GridViewIcon } from '../../elements/icons/GridViewIcon' import { CaretDownIcon } from '../../elements/icons/CaretDownIcon' -import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon' +import { PinnedButtons } from './PinnedButtons' +import { usePersistedState } from '../../../lib/hooks/usePersistedState' +import { PinnedSearch } from '../../../pages/settings/pinned-searches' export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search' @@ -62,7 +64,30 @@ type LibraryHeaderProps = { ) => Promise } +const controlWidths = ( + layout: LayoutType, + multiSelectMode: MultiSelectMode +) => { + return { + width: '95%', + '@mdDown': { + width: multiSelectMode !== 'off' ? '100%' : '95%', + display: multiSelectMode !== 'off' ? 'flex' : 'none', + }, + '@media (min-width: 930px)': { + width: layout == 'GRID_LAYOUT' ? '660px' : '640px', + }, + '@media (min-width: 1280px)': { + width: '1000px', + }, + '@media (min-width: 1600px)': { + width: '1340px', + }, + } +} + export function LibraryHeader(props: LibraryHeaderProps): JSX.Element { + const headerHeight = useGetHeaderHeight() return ( <> ({ + key: `--library-pinned-searches`, + initialValue: [], + isSessionStorage: false, + }) + return ( - + + + + + + ) } @@ -617,20 +671,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element { distribution={props.multiSelectMode !== 'off' ? 'center' : 'start'} css={{ gap: '10px', - width: '95%', - '@mdDown': { - width: props.multiSelectMode !== 'off' ? '100%' : '95%', - display: props.multiSelectMode !== 'off' ? 'flex' : 'none', - }, - '@media (min-width: 930px)': { - width: props.layout == 'GRID_LAYOUT' ? '660px' : '640px', - }, - '@media (min-width: 1280px)': { - width: '1000px', - }, - '@media (min-width: 1600px)': { - width: '1340px', - }, + ...controlWidths(props.layout, props.multiSelectMode), }} > diff --git a/packages/web/components/templates/homeFeed/PinnedButtons.tsx b/packages/web/components/templates/homeFeed/PinnedButtons.tsx new file mode 100644 index 000000000..981ddefa4 --- /dev/null +++ b/packages/web/components/templates/homeFeed/PinnedButtons.tsx @@ -0,0 +1,105 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import { HStack, SpanBox } from '../../elements/LayoutPrimitives' +import { theme } from '../../tokens/stitches.config' +import { Button } from '../../elements/Button' + +import { Dropdown, DropdownOption } from '../../elements/DropdownElements' +import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon' +import { PinnedSearch } from '../../../pages/settings/pinned-searches' +import { useRouter } from 'next/router' +import { usePersistedState } from '../../../lib/hooks/usePersistedState' + +type PinnedButtonsProps = { + items: PinnedSearch[] + searchTerm: string | undefined + applySearchQuery: (searchQuery: string) => void +} + +export const PinnedButtons = (props: PinnedButtonsProps): JSX.Element => { + const router = useRouter() + const [hidePinnedSearches, setHidePinnedSearches] = usePersistedState({ + key: '--library-hide-pinned-searches', + initialValue: false, + isSessionStorage: false, + }) + + if (hidePinnedSearches || !props.items.length) { + return <> + } + + return ( + + {props.items.map((item) => { + const style = + item.search == props.searchTerm ? 'ctaPill' : 'ctaPillUnselected' + return ( + + ) + })} + + + + } + css={{}} + children={ + <> + { + router.push('/settings/pinned-searches') + }} + title="Edit" + /> + { + setHidePinnedSearches(true) + }} + title="Hide" + /> + + } + > + + ) +} diff --git a/packages/web/components/templates/reader/ReaderHeader.tsx b/packages/web/components/templates/reader/ReaderHeader.tsx index 011b9ddd7..76287962a 100644 --- a/packages/web/components/templates/reader/ReaderHeader.tsx +++ b/packages/web/components/templates/reader/ReaderHeader.tsx @@ -1,10 +1,9 @@ import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' import { PrimaryDropdown } from '../PrimaryDropdown' -import { TooltipWrapped } from '../../elements/Tooltip' import { LogoBox } from '../../elements/LogoBox' import { ReactNode } from 'react' -import { HEADER_HEIGHT } from '../homeFeed/HeaderSpacer' +import { useGetHeaderHeight } from '../homeFeed/HeaderSpacer' import { theme } from '../../tokens/stitches.config' import { ReaderSettingsIcon } from '../../elements/icons/ReaderSettingsIcon' import { CircleUtilityMenuIcon } from '../../elements/icons/CircleUtilityMenuIcon' @@ -17,6 +16,7 @@ type ReaderHeaderProps = { } export function ReaderHeader(props: ReaderHeaderProps): JSX.Element { + const headerHeight = useGetHeaderHeight() return ( <> { + switch (action.type) { + case 'RESET': { + const itemStr = window['localStorage'].getItem(PINNED_SEARCHES_KEY) + if (itemStr) { + try { + const parsed = JSON.parse(itemStr) + if (Array.isArray(parsed)) { + return { state: 'CURRENT', items: parsed as PinnedSearch[] } + } + } 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.itemId == item.itemId + ) + 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.itemId != item.itemId + ) + return { state: 'CURRENT', items: [...updated] } + } + default: + throw new Error('unknown action') + } + } + + const [pinnedSearches, dispatchList] = useReducer(listReducer, { + state: 'INITIAL', + items: [], + }) + + const items = useMemo(() => { + if (pinnedSearches.state == 'INITIAL') { + return { labelItems: [], savedSearchItems: [] } + } + const labelItems = labels.map((label) => { + return { + label, + isSelected: !!pinnedSearches.items.find( + (ps) => ps.type == 'label' && ps.itemId == label.id + ), + } + }) + const savedSearchItems = (savedSearches ?? []).map((savedSearch) => { + return { + savedSearch, + isSelected: !!pinnedSearches.items.find( + (ps) => ps.type == 'saved-search' && ps.itemId == savedSearch.id + ), + } + }) + return { labelItems, savedSearchItems } + }, [pinnedSearches, labels, savedSearches]) + + useEffect(() => { + try { + // Only write updated state to local storage + if (pinnedSearches.state == 'CURRENT') { + window['localStorage'].setItem( + PINNED_SEARCHES_KEY, + JSON.stringify(pinnedSearches.items) + ) + } + } catch (error) { + console.log('error": ', error) + } + }, [pinnedSearches]) + + useEffect(() => { + dispatchList({ type: 'RESET' }) + }, []) + + applyStoredTheme(false) + + return ( + + + +
+ + + + + Pinned Searches + + + Pin up to five searches from your labels or saved searches. + + + + + + { + setHidePinnedSearches(!event.currentTarget.checked) + }} + style={{ padding: '0px', margin: '0px' }} + /> + Enable Pinned Searches + + + {!hidePinnedSearches && ( + <> + + Saved Searches + + {items.savedSearchItems.map((item) => { + return ( + + ) + })} + + + Labels + + {items.labelItems.map((item) => { + return ( + + ) + })} + + )} + + + +
+
+ ) +} + +type LabelButtonProps = { + label: Label + isSelected: boolean + listAction: (arg: { + type: ListAction + item?: PinnedSearch | undefined + }) => void +} + +function LabelButton(props: LabelButtonProps): JSX.Element { + const labelId = `checkbox-label-${props.label.id}` + + const changeState = useCallback( + (newState: boolean) => { + if (!newState) { + props.listAction({ + type: 'REMOVE_ITEM', + item: { + type: 'label', + itemId: props.label.id, + name: props.label.name, + search: `label:\"${props.label.name}\"`, + }, + }) + } else { + props.listAction({ + type: 'ADD_ITEM', + item: { + type: 'label', + itemId: props.label.id, + name: props.label.name, + search: `label:\"${props.label.name}\"`, + }, + }) + } + }, + [props] + ) + + return ( + { + changeState(!props.isSelected) + event.preventDefault() + }} + > + { + changeState(event.currentTarget.checked) + event.preventDefault() + }} + /> + {props.label.name} + + + ) +} + +type SearchButtonProps = { + search: SavedSearch + isSelected: boolean + listAction: (arg: { + type: ListAction + item?: PinnedSearch | undefined + }) => void +} + +function SearchButton(props: SearchButtonProps): JSX.Element { + const searchId = `checkbox-search-${props.search.id}` + return ( + { + if (props.isSelected) { + props.listAction({ + type: 'REMOVE_ITEM', + item: { + type: 'saved-search', + itemId: props.search.id, + name: props.search.name, + search: props.search.filter, + }, + }) + } else { + props.listAction({ + type: 'ADD_ITEM', + item: { + type: 'saved-search', + itemId: props.search.id, + name: props.search.name, + search: props.search.filter, + }, + }) + } + event.preventDefault() + }} + > + {}} /> + {props.search.name} + + ) +} From 2e650c4ad1edcad16e2a441047c4daf3f9a2a8fb Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 8 Nov 2023 12:10:45 +0800 Subject: [PATCH 8/9] Simplify change actions --- .../web/pages/settings/pinned-searches.tsx | 316 ++++++++---------- 1 file changed, 141 insertions(+), 175 deletions(-) diff --git a/packages/web/pages/settings/pinned-searches.tsx b/packages/web/pages/settings/pinned-searches.tsx index f7d85437e..819278b78 100644 --- a/packages/web/pages/settings/pinned-searches.tsx +++ b/packages/web/pages/settings/pinned-searches.tsx @@ -1,13 +1,11 @@ -import { +import React, { + ReactNode, useCallback, useEffect, useMemo, useReducer, - useRef, - useState, } from 'react' import { Toaster } from 'react-hot-toast' -import { Button } from '../../components/elements/Button' import { Box, HStack, @@ -16,25 +14,13 @@ import { } from '../../components/elements/LayoutPrimitives' import { StyledText } from '../../components/elements/StyledText' import { SettingsLayout } from '../../components/templates/SettingsLayout' -import { styled, theme } from '../../components/tokens/stitches.config' -import { updateEmailMutation } from '../../lib/networking/mutations/updateEmailMutation' -import { updateUserMutation } from '../../lib/networking/mutations/updateUserMutation' -import { updateUserProfileMutation } from '../../lib/networking/mutations/updateUserProfileMutation' -import { useGetLibraryItemsQuery } from '../../lib/networking/queries/useGetLibraryItemsQuery' -import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' -import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery' import { applyStoredTheme } from '../../lib/themeUpdater' -import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' -import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' -import { ProgressBar } from '../../components/elements/ProgressBar' import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery' import { useGetSavedSearchQuery } from '../../lib/networking/queries/useGetSavedSearchQuery' -import CheckboxComponent from '../../components/elements/Checkbox' import { Label } from '../../lib/networking/fragments/labelFragment' -import { Circle, ToggleLeft } from 'phosphor-react' +import { Circle } from 'phosphor-react' import { SavedSearch } from '../../lib/networking/fragments/savedSearchFragment' import { usePersistedState } from '../../lib/hooks/usePersistedState' -import { StyledToggleButton } from '../../components/templates/PrimaryDropdown' export type PinnedSearch = { type: 'saved-search' | 'label' @@ -162,92 +148,89 @@ export default function PinnedSearches(): JSX.Element { top: '5rem', }} /> - -
+ + + + Pinned Searches + + + Pin up to five searches from your labels or saved searches. + + + - - - Pinned Searches - - - Pin up to five searches from your labels or saved searches. - - + + { + setHidePinnedSearches(!event.currentTarget.checked) + }} + style={{ padding: '0px', margin: '0px' }} + /> + Enable Pinned Searches + - - - { - setHidePinnedSearches(!event.currentTarget.checked) - }} - style={{ padding: '0px', margin: '0px' }} - /> - Enable Pinned Searches - + {!hidePinnedSearches && ( + <> + + Saved Searches + + {items.savedSearchItems.map((item) => { + return ( + + ) + })} - {!hidePinnedSearches && ( - <> - - Saved Searches - - {items.savedSearchItems.map((item) => { - return ( - - ) - })} - - - Labels - - {items.labelItems.map((item) => { - return ( - - ) - })} - - )} - + + Labels + + {items.labelItems.map((item) => { + return ( + + ) + })} + + )} - +
) } @@ -263,78 +246,28 @@ type LabelButtonProps = { function LabelButton(props: LabelButtonProps): JSX.Element { const labelId = `checkbox-label-${props.label.id}` - - const changeState = useCallback( - (newState: boolean) => { - if (!newState) { - props.listAction({ - type: 'REMOVE_ITEM', - item: { - type: 'label', - itemId: props.label.id, - name: props.label.name, - search: `label:\"${props.label.name}\"`, - }, - }) - } else { - props.listAction({ - type: 'ADD_ITEM', - item: { - type: 'label', - itemId: props.label.id, - name: props.label.name, - search: `label:\"${props.label.name}\"`, - }, - }) - } - }, - [props] - ) - return ( - { - changeState(!props.isSelected) - event.preventDefault() + isSelected={props.isSelected} + item={{ + type: 'label', + itemId: props.label.id, + name: props.label.name, + search: `label:\"${props.label.name}\"`, }} + listAction={props.listAction} > { - changeState(event.currentTarget.checked) - event.preventDefault() - }} + // eslint-disable-next-line @typescript-eslint/no-empty-function + onChange={(event) => {}} /> {props.label.name} - + ) } @@ -350,9 +283,63 @@ type SearchButtonProps = { function SearchButton(props: SearchButtonProps): JSX.Element { const searchId = `checkbox-search-${props.search.id}` return ( - + {}} + /> + {props.search.name} + + ) +} + +type CheckboxButtonProps = { + key: string + title: string + isSelected: boolean + item: PinnedSearch + + listAction: (arg: { + type: ListAction + item?: PinnedSearch | 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 ( + { - if (props.isSelected) { - props.listAction({ - type: 'REMOVE_ITEM', - item: { - type: 'saved-search', - itemId: props.search.id, - name: props.search.name, - search: props.search.filter, - }, - }) - } else { - props.listAction({ - type: 'ADD_ITEM', - item: { - type: 'saved-search', - itemId: props.search.id, - name: props.search.name, - search: props.search.filter, - }, - }) - } + handleChange(!props.isSelected) event.preventDefault() }} > - {}} /> - {props.search.name} + {props.children} ) } From 656ac8e6762d80947a74883aa89b2a328fc31d16 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 8 Nov 2023 12:21:57 +0800 Subject: [PATCH 9/9] Fix dropdown --- .../templates/homeFeed/PinnedButtons.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/web/components/templates/homeFeed/PinnedButtons.tsx b/packages/web/components/templates/homeFeed/PinnedButtons.tsx index 981ddefa4..5b62ad40c 100644 --- a/packages/web/components/templates/homeFeed/PinnedButtons.tsx +++ b/packages/web/components/templates/homeFeed/PinnedButtons.tsx @@ -83,23 +83,20 @@ export const PinnedButtons = (props: PinnedButtonsProps): JSX.Element => { } css={{}} - children={ - <> - { - router.push('/settings/pinned-searches') - }} - title="Edit" - /> - { - setHidePinnedSearches(true) - }} - title="Hide" - /> - - } - > + > + { + router.push('/settings/pinned-searches') + }} + title="Edit" + /> + { + setHidePinnedSearches(true) + }} + title="Hide" + /> + ) }