mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Refresh labels when edited
This commit is contained in:
parent
e2aeff0c6f
commit
0bb2c1d8c6
8 changed files with 98 additions and 76 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import Link from 'next/link'
|
||||
import { SpanBox } from './LayoutPrimitives'
|
||||
import { StyledText } from './StyledText'
|
||||
|
||||
type LabelChipProps = {
|
||||
|
|
@ -16,19 +18,24 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
|
|||
}
|
||||
const color = hexToRgb(props.color)
|
||||
return (
|
||||
<StyledText
|
||||
css={{
|
||||
margin: '4px',
|
||||
borderRadius: '32px',
|
||||
color: props.color,
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
padding: '4px 8px 4px 8px',
|
||||
border: `1px solid rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.7)`,
|
||||
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.08)`,
|
||||
}}
|
||||
>
|
||||
{props.text}
|
||||
</StyledText>
|
||||
<Link href={`/home?q=label:"${props.text}"`}>
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'inline-table',
|
||||
margin: '4px',
|
||||
borderRadius: '32px',
|
||||
color: props.color,
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
padding: '4px 8px 4px 8px',
|
||||
whiteSpace: 'nowrap',
|
||||
cursor: 'pointer',
|
||||
border: `1px solid rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.7)`,
|
||||
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.08)`,
|
||||
}}
|
||||
>
|
||||
{props.text}
|
||||
</SpanBox>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Separator } from "@radix-ui/react-separator"
|
||||
import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from "phosphor-react"
|
||||
import { useRef } from "react"
|
||||
import { ArticleAttributes } from "../../../lib/networking/queries/useGetArticleQuery"
|
||||
import { useGetUserPreferences, UserPreferences } from "../../../lib/networking/queries/useGetUserPreferences"
|
||||
import { useGetUserPreferences } from "../../../lib/networking/queries/useGetUserPreferences"
|
||||
import { Button } from "../../elements/Button"
|
||||
import { Dropdown } from "../../elements/DropdownElements"
|
||||
import { Box, SpanBox } from "../../elements/LayoutPrimitives"
|
||||
|
|
@ -15,7 +14,7 @@ export type ArticleActionsMenuLayout = 'horizontal' | 'vertical'
|
|||
type ArticleActionsMenuProps = {
|
||||
article: ArticleAttributes
|
||||
layout: ArticleActionsMenuLayout
|
||||
articleActionHandler: (action: string, arg?: number) => void
|
||||
articleActionHandler: (action: string, arg?: unknown) => void
|
||||
}
|
||||
|
||||
type MenuSeparatorProps = {
|
||||
|
|
@ -91,7 +90,10 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
|
|||
</SpanBox>
|
||||
}
|
||||
>
|
||||
<EditLabelsControl article={props.article} />
|
||||
<EditLabelsControl
|
||||
article={props.article}
|
||||
articleActionHandler={props.articleActionHandler}
|
||||
/>
|
||||
</ActionDropdown>
|
||||
{/*
|
||||
<Button onClick={() => props.articleActionHandler('editLabels')} css={{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
|
||||
import { Article } from './../../../components/templates/article/Article'
|
||||
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
|
||||
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
|
||||
import { StyledText } from './../../elements/StyledText'
|
||||
import { ArticleSubtitle } from './../../patterns/ArticleSubtitle'
|
||||
import { theme, ThemeId } from './../../tokens/stitches.config'
|
||||
|
|
@ -14,9 +14,11 @@ import { userPersonalizationMutation } from '../../../lib/networking/mutations/u
|
|||
import { updateThemeLocally } from '../../../lib/themeUpdater'
|
||||
import { ArticleMutations } from '../../../lib/articleActions'
|
||||
import { LabelChip } from '../../elements/LabelChip'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
|
||||
type ArticleContainerProps = {
|
||||
article: ArticleAttributes
|
||||
labels: Label[]
|
||||
articleMutations: ArticleMutations
|
||||
scrollElementRef: MutableRefObject<HTMLDivElement | null>
|
||||
isAppleAppEmbed: boolean
|
||||
|
|
@ -29,14 +31,10 @@ type ArticleContainerProps = {
|
|||
|
||||
export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
||||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const [showLabelsModal, setShowLabelsModal] = useState(false)
|
||||
const [showNotesSidebar, setShowNotesSidebar] = useState(false)
|
||||
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
|
||||
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
|
||||
const [margin, setMargin] = useState(props.margin ?? 360)
|
||||
const [labels, setLabels] = useState(
|
||||
props.article.labels?.map((l) => l.id) || []
|
||||
)
|
||||
|
||||
const updateFontSize = async (newFontSize: number) => {
|
||||
if (fontSize !== newFontSize) {
|
||||
|
|
@ -159,11 +157,13 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
author={props.article.author}
|
||||
href={props.article.url}
|
||||
/>
|
||||
<HStack>
|
||||
{props.article.labels?.map((label) =>
|
||||
<LabelChip key={label.id} text={label.name} color={label.color} />
|
||||
)}
|
||||
</HStack>
|
||||
{props.labels ? (
|
||||
<SpanBox css={{ py: '16px', width: '100%' }}>
|
||||
{props.labels?.map((label) =>
|
||||
<LabelChip key={label.id} text={label.name} color={label.color} />
|
||||
)}
|
||||
</SpanBox>
|
||||
) : null}
|
||||
<ArticleHeaderToolbar
|
||||
articleTitle={props.article.title}
|
||||
articleShareURL={props.highlightsBaseURL}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticle
|
|||
import { Check, Circle, PencilSimple, Plus } from 'phosphor-react'
|
||||
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
|
||||
|
||||
type EditLabelsControlProps = {
|
||||
// labels: Label[]
|
||||
article: ArticleAttributes
|
||||
// onOpenChange: (open: boolean) => void
|
||||
// setLabels: (labels: Label[]) => void
|
||||
articleActionHandler: (action: string, arg?: unknown) => void
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
|
|
@ -170,7 +169,6 @@ export function EditLabelsControl(props: EditLabelsControlProps): JSX.Element {
|
|||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [filterText, setFilterText] = useState('')
|
||||
const { labels } = useGetLabelsQuery()
|
||||
|
||||
const [selectedLabels, setSelectedLabels] = useState<Label[]>(props.article.labels || [])
|
||||
|
||||
const isSelected = useCallback((label: Label): boolean => {
|
||||
|
|
@ -179,14 +177,25 @@ export function EditLabelsControl(props: EditLabelsControlProps): JSX.Element {
|
|||
})
|
||||
}, [selectedLabels])
|
||||
|
||||
const toggleLabel = useCallback((label: Label) => {
|
||||
const toggleLabel = useCallback(async (label: Label) => {
|
||||
let newSelectedLabels = [...selectedLabels]
|
||||
if (isSelected(label)) {
|
||||
setSelectedLabels(selectedLabels.filter((other) => {
|
||||
newSelectedLabels = selectedLabels.filter((other) => {
|
||||
return other.id !== label.id
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
setSelectedLabels([...selectedLabels, label])
|
||||
newSelectedLabels = [...selectedLabels, label]
|
||||
}
|
||||
setSelectedLabels(newSelectedLabels)
|
||||
|
||||
const result = await setLabelsMutation(
|
||||
props.article.linkId,
|
||||
newSelectedLabels.map((label) => label.id)
|
||||
)
|
||||
props.article.labels = result
|
||||
props.articleActionHandler('refreshLabels', result)
|
||||
|
||||
console.log('refreshing article with labels', props.article.labels)
|
||||
}, [isSelected, selectedLabels])
|
||||
|
||||
const filteredLabels = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
import { gql } from 'graphql-request'
|
||||
import { Label, labelFragment } from '../fragments/labelFragment'
|
||||
import { gqlFetcher } from '../networkHelpers'
|
||||
|
||||
type SetLabelsResult = {
|
||||
setLabels: SetLabels
|
||||
errorCodes?: unknown[]
|
||||
}
|
||||
|
||||
type SetLabels = {
|
||||
labels: Label[]
|
||||
}
|
||||
|
||||
export async function setLabelsMutation(
|
||||
pageId: string,
|
||||
linkId: string,
|
||||
labelIds: string[]
|
||||
): Promise<any | undefined> {
|
||||
): Promise<Label[] | undefined> {
|
||||
const mutation = gql`
|
||||
mutation SetLabels($input: SetLabelsInput!) {
|
||||
setLabels(input: $input) {
|
||||
... on SetLabelsSuccess {
|
||||
labels {
|
||||
id
|
||||
...LabelFields
|
||||
}
|
||||
}
|
||||
... on SetLabelsError {
|
||||
|
|
@ -18,12 +28,12 @@ export async function setLabelsMutation(
|
|||
}
|
||||
}
|
||||
}
|
||||
${labelFragment}
|
||||
`
|
||||
|
||||
try {
|
||||
const data = await gqlFetcher(mutation, { input: { pageId, labelIds } })
|
||||
console.log(data)
|
||||
return data
|
||||
const data = await gqlFetcher(mutation, { input: { linkId, labelIds } }) as SetLabelsResult
|
||||
return data.errorCodes ? undefined : data.setLabels.labels
|
||||
} catch (error) {
|
||||
console.log('SetLabelsOutput error', error)
|
||||
return undefined
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { gql } from 'graphql-request'
|
||||
import useSWRImmutable from 'swr'
|
||||
import useSWR from 'swr'
|
||||
import { makeGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
|
||||
import { articleFragment, ContentReader } from '../fragments/articleFragment'
|
||||
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
|
||||
import { ScopedMutator } from 'swr/dist/types'
|
||||
import { KeyedMutator, ScopedMutator } from 'swr/dist/types'
|
||||
import { Label, labelFragment } from '../fragments/labelFragment'
|
||||
|
||||
type ArticleQueryInput = {
|
||||
|
|
@ -16,6 +17,7 @@ type ArticleQueryOutput = {
|
|||
articleData?: ArticleData
|
||||
articleFetchError: unknown
|
||||
isLoading: boolean
|
||||
mutate: KeyedMutator<unknown>
|
||||
}
|
||||
|
||||
type ArticleData = {
|
||||
|
|
@ -99,7 +101,7 @@ export function useGetArticleQuery({
|
|||
includeFriendsHighlights,
|
||||
}
|
||||
|
||||
const { data, error } = useSWRImmutable(
|
||||
const { data, error, mutate } = useSWR(
|
||||
slug ? [query, username, slug, includeFriendsHighlights] : null,
|
||||
makeGqlFetcher(variables)
|
||||
)
|
||||
|
|
@ -110,12 +112,14 @@ export function useGetArticleQuery({
|
|||
// it will be nested in the data pages, if there is one error,
|
||||
// we invalidate the data and return the error. We also zero out
|
||||
// the response in the case of an error.
|
||||
console.log('result data', resultData)
|
||||
if (!error && resultData && resultData.article.errorCodes) {
|
||||
resultError = resultData.article.errorCodes
|
||||
resultData = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
mutate,
|
||||
articleData: resultData,
|
||||
articleFetchError: resultError as unknown,
|
||||
isLoading: !error && !data,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
|
|||
isValidating,
|
||||
emailAddresses,
|
||||
revalidate: () => {
|
||||
mutate()
|
||||
mutate(undefined, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useRouter } from 'next/router'
|
|||
import { VStack } from './../../../components/elements/LayoutPrimitives'
|
||||
import { ArticleContainer } from './../../../components/templates/article/ArticleContainer'
|
||||
import { PdfArticleContainerProps } from './../../../components/templates/article/PdfArticleContainer'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { articleKeyboardCommands, navigationCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
|
@ -20,26 +20,12 @@ import { articleReadingProgressMutation } from '../../../lib/networking/mutation
|
|||
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
|
||||
import Script from 'next/script'
|
||||
import { EditLabelsControl } from '../../../components/templates/article/EditLabelsControl'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { isVipUser } from '../../../lib/featureFlag'
|
||||
import { styled, theme } from '../../../components/tokens/stitches.config'
|
||||
import { Button } from '../../../components/elements/Button'
|
||||
import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from 'phosphor-react'
|
||||
import { Separator } from '@radix-ui/react-separator'
|
||||
import { Article } from '../../../components/templates/article/Article'
|
||||
import { theme } from '../../../components/tokens/stitches.config'
|
||||
import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu'
|
||||
import { HighlightsModal } from '../../../components/templates/article/HighlightsModal'
|
||||
import { setLinkArchivedMutation } from '../../../lib/networking/mutations/setLinkArchivedMutation'
|
||||
import { EditLabelsModal } from '../../../components/templates/article/EditLabelsModal'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
|
||||
const MenuSeparator = styled(Separator, {
|
||||
width: '100%',
|
||||
margin: 0,
|
||||
backgroundColor: 'red',
|
||||
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
|
||||
my: '8px',
|
||||
})
|
||||
|
||||
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
|
||||
() => import('./../../../components/templates/article/PdfArticleContainer'),
|
||||
|
|
@ -50,20 +36,28 @@ export default function Home(): JSX.Element {
|
|||
const router = useRouter()
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null)
|
||||
const { slug } = router.query
|
||||
const [showLabelsModal, setShowLabelsModal] = useState(false)
|
||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
|
||||
|
||||
// Populate data cache
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const { articleData } = useGetArticleQuery({
|
||||
|
||||
const { preferencesData } = useGetUserPreferences()
|
||||
const [fontSize, setFontSize] = useState(preferencesData?.fontSize ?? 20)
|
||||
const [marginWidth, setMarginWidth] = useState(preferencesData?.margin ?? 360)
|
||||
|
||||
const { articleData, mutate } = useGetArticleQuery({
|
||||
username: router.query.username as string,
|
||||
slug: router.query.slug as string,
|
||||
includeFriendsHighlights: false,
|
||||
})
|
||||
const { preferencesData } = useGetUserPreferences()
|
||||
const article = articleData?.article.article
|
||||
const [fontSize, setFontSize] = useState(preferencesData?.fontSize ?? 20)
|
||||
const [marginWidth, setMarginWidth] = useState(preferencesData?.margin ?? 360)
|
||||
|
||||
const [labels, setLabels] = useState<Label[]>([])
|
||||
useEffect(() => {
|
||||
if (article?.labels) {
|
||||
setLabels(article.labels)
|
||||
}
|
||||
}, [article])
|
||||
|
||||
useKeyboardShortcuts(navigationCommands(router))
|
||||
|
||||
|
|
@ -73,11 +67,10 @@ export default function Home(): JSX.Element {
|
|||
}
|
||||
|
||||
const updateMarginWidth = async (newMargin: number) => {
|
||||
console.log('margin', newMargin)
|
||||
setMarginWidth(newMargin)
|
||||
}
|
||||
|
||||
const actionHandler = async (action: string, arg?: number) => {
|
||||
const actionHandler = async (action: string, arg?: unknown) => {
|
||||
switch (action) {
|
||||
case 'archive':
|
||||
if (article) {
|
||||
|
|
@ -96,6 +89,9 @@ export default function Home(): JSX.Element {
|
|||
window.open(url, '_blank')
|
||||
}
|
||||
break
|
||||
case 'refreshLabels':
|
||||
setLabels(arg as Label[])
|
||||
break
|
||||
case 'showHighlights':
|
||||
setShowHighlightsModal(true)
|
||||
break
|
||||
|
|
@ -111,10 +107,6 @@ export default function Home(): JSX.Element {
|
|||
case 'decrementMarginWidth':
|
||||
updateMarginWidth(Math.max(marginWidth - 50, 200))
|
||||
break
|
||||
case 'editLabels':
|
||||
setShowLabelsModal(true)
|
||||
console.log('showing labels modal')
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -194,6 +186,7 @@ export default function Home(): JSX.Element {
|
|||
highlightsBaseURL={`${webBaseURL}/${viewerData.me?.profile?.username}/${slug}/highlights`}
|
||||
fontSize={fontSize}
|
||||
margin={marginWidth}
|
||||
labels={labels}
|
||||
articleMutations={{
|
||||
createHighlightMutation,
|
||||
deleteHighlightMutation,
|
||||
|
|
@ -213,9 +206,6 @@ export default function Home(): JSX.Element {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
{showLabelsModal && (
|
||||
<EditLabelsModal />
|
||||
)}
|
||||
</PrimaryLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue