mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #4040 from omnivore-app/feat/web-layout-navigation
Update layout navigation with left menu
This commit is contained in:
commit
e9f3009111
113 changed files with 4045 additions and 1467 deletions
|
|
@ -8,6 +8,23 @@ import {
|
|||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
import { User } from './user'
|
||||
import { Label } from './label'
|
||||
|
||||
export type ShortcutType = 'search' | 'label' | 'newsletter' | 'feed' | 'folder'
|
||||
|
||||
export type Shortcut = {
|
||||
type: ShortcutType
|
||||
|
||||
id: string
|
||||
name: string
|
||||
section: string
|
||||
filter?: string
|
||||
|
||||
icon?: string
|
||||
label?: Label
|
||||
|
||||
children?: Shortcut[]
|
||||
}
|
||||
|
||||
@Entity({ name: 'user_personalization' })
|
||||
export class UserPersonalization {
|
||||
|
|
@ -59,4 +76,7 @@ export class UserPersonalization {
|
|||
|
||||
@Column('jsonb')
|
||||
digestConfig?: any | null
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
shortcuts?: any | null // Explicitly allow null values
|
||||
}
|
||||
|
|
|
|||
107
packages/api/src/routers/shortcuts_router.ts
Normal file
107
packages/api/src/routers/shortcuts_router.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import { env } from '../env'
|
||||
import { getClaimsByToken, getTokenByRequest } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { logger } from '../utils/logger'
|
||||
import {
|
||||
getShortcuts,
|
||||
resetShortcuts,
|
||||
setShortcuts,
|
||||
} from '../services/user_personalization'
|
||||
|
||||
export function shortcutsRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/', cors<express.Request>(corsConfig), async (req, res) => {
|
||||
logger.info('get shortcuts router')
|
||||
const token = getTokenByRequest(req)
|
||||
|
||||
let claims
|
||||
try {
|
||||
claims = await getClaimsByToken(token)
|
||||
if (!claims) {
|
||||
logger.info('failed to authorize')
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info('failed to authorize', e)
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
|
||||
try {
|
||||
const shortcuts = await getShortcuts(claims.uid)
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
})
|
||||
} catch (e) {
|
||||
logger.info('error getting shortcuts', e)
|
||||
}
|
||||
|
||||
return res.status(500).send('UNKNOWN')
|
||||
})
|
||||
|
||||
router.options('/', cors<express.Request>({ ...corsConfig, maxAge: 600 }))
|
||||
router.put('/', cors<express.Request>(corsConfig), async (req, res) => {
|
||||
logger.info('put shortcuts router')
|
||||
const token = getTokenByRequest(req)
|
||||
|
||||
let claims
|
||||
try {
|
||||
claims = await getClaimsByToken(token)
|
||||
if (!claims) {
|
||||
logger.info('failed to authorize')
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info('failed to authorize', e)
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
|
||||
try {
|
||||
const shortcuts = await setShortcuts(claims.uid, req.body.shortcuts)
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
})
|
||||
} catch (e) {
|
||||
logger.info('error settings shortcuts', e)
|
||||
}
|
||||
|
||||
return res.status(500).send('UNKNOWN')
|
||||
})
|
||||
|
||||
router.delete('/', cors<express.Request>(corsConfig), async (req, res) => {
|
||||
logger.info('delete shortcuts router')
|
||||
const token = getTokenByRequest(req)
|
||||
|
||||
let claims
|
||||
try {
|
||||
claims = await getClaimsByToken(token)
|
||||
if (!claims) {
|
||||
logger.info('failed to authorize')
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info('failed to authorize', e)
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await resetShortcuts(claims.uid)
|
||||
if (success) {
|
||||
const shortcuts = await getShortcuts(claims.uid)
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info('error settings shortcuts', e)
|
||||
}
|
||||
|
||||
return res.status(500).send('UNKNOWN')
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ import { corsConfig } from './utils/corsConfig'
|
|||
import { getClientFromUserAgent } from './utils/helpers'
|
||||
import { buildLogger, buildLoggerTransport, logger } from './utils/logger'
|
||||
import { apiLimiter, authLimiter } from './utils/rate_limit'
|
||||
import { shortcutsRouter } from './routers/shortcuts_router'
|
||||
|
||||
const PORT = process.env.PORT || 4000
|
||||
|
||||
|
|
@ -94,6 +95,7 @@ export const createApp = (): Express => {
|
|||
app.use('/api/mobile-auth', authLimiter, mobileAuthRouter())
|
||||
app.use('/api/page', pageRouter())
|
||||
app.use('/api/user', userRouter())
|
||||
app.use('/api/shortcuts', shortcutsRouter())
|
||||
app.use('/api/article', articleRouter())
|
||||
app.use('/api/ai-summary', aiSummariesRouter())
|
||||
app.use('/api/explain', explainRouter())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { DeepPartial } from 'typeorm'
|
||||
import { UserPersonalization } from '../entity/user_personalization'
|
||||
import { DeepPartial, IsNull } from 'typeorm'
|
||||
import { Shortcut, UserPersonalization } from '../entity/user_personalization'
|
||||
import { authTrx } from '../repository'
|
||||
import { findLabelsByUserId } from './labels'
|
||||
import { findSubscriptionById } from './subscriptions'
|
||||
import { Filter } from '../entity/filter'
|
||||
import { Subscription, SubscriptionStatus } from '../entity/subscription'
|
||||
|
||||
export const findUserPersonalization = async (userId: string) => {
|
||||
return authTrx(
|
||||
|
|
@ -34,3 +38,132 @@ export const saveUserPersonalization = async (
|
|||
userId
|
||||
)
|
||||
}
|
||||
|
||||
export const getShortcuts = async (userId: string): Promise<Shortcut[]> => {
|
||||
const personalization = await authTrx(
|
||||
(t) =>
|
||||
t.getRepository(UserPersonalization).findOneBy({
|
||||
user: { id: userId },
|
||||
}),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
if (personalization?.shortcuts) {
|
||||
return personalization?.shortcuts as Shortcut[]
|
||||
}
|
||||
|
||||
return await userDefaultShortcuts(userId)
|
||||
}
|
||||
|
||||
export const resetShortcuts = async (userId: string): Promise<boolean> => {
|
||||
const result = await authTrx(
|
||||
(t) => {
|
||||
return t
|
||||
.createQueryBuilder()
|
||||
.update(UserPersonalization)
|
||||
.set({ shortcuts: () => 'null' }) // Use a raw SQL string to set the value to null
|
||||
.where({
|
||||
user: { id: userId },
|
||||
})
|
||||
.execute()
|
||||
},
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
if (!result) {
|
||||
throw Error('Could not update shortcuts')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const setShortcuts = async (
|
||||
userId: string,
|
||||
shortcuts: Shortcut[]
|
||||
): Promise<Shortcut[]> => {
|
||||
const result = await authTrx(
|
||||
(t) =>
|
||||
t.getRepository(UserPersonalization).update(
|
||||
{
|
||||
user: { id: userId },
|
||||
},
|
||||
{
|
||||
shortcuts: shortcuts,
|
||||
}
|
||||
),
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
if (!result.affected || result.affected < 1) {
|
||||
throw Error('Could not update shortcuts')
|
||||
}
|
||||
return shortcuts
|
||||
}
|
||||
|
||||
const userDefaultShortcuts = async (userId: string): Promise<Shortcut[]> => {
|
||||
const labels = await findLabelsByUserId(userId)
|
||||
const savedSearches = await authTrx((t) =>
|
||||
t.getRepository(Filter).find({
|
||||
where: { user: { id: userId } },
|
||||
order: { position: 'ASC' },
|
||||
})
|
||||
)
|
||||
const subscriptions = await authTrx((t) =>
|
||||
t.getRepository(Subscription).find({
|
||||
where: { user: { id: userId }, status: SubscriptionStatus.Active },
|
||||
order: { mostRecentItemDate: 'DESC' },
|
||||
})
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
id: '1',
|
||||
type: 'folder',
|
||||
name: 'Labels',
|
||||
section: 'library',
|
||||
children: labels.map((label) => {
|
||||
return {
|
||||
id: label.id,
|
||||
type: 'label',
|
||||
name: label.name,
|
||||
section: 'library',
|
||||
label: label,
|
||||
filter: `in:all label:"${label.name}"`,
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'folder',
|
||||
name: 'Subscriptions',
|
||||
section: 'subscriptions',
|
||||
children: subscriptions.map((subscription) => {
|
||||
return {
|
||||
id: subscription.id,
|
||||
type: subscription.type == 'NEWSLETTER' ? 'newsletter' : 'feed',
|
||||
name: subscription.name,
|
||||
section: 'subscriptions',
|
||||
icon: subscription.icon ?? undefined,
|
||||
filter:
|
||||
subscription.type == 'NEWSLETTER'
|
||||
? `in:following subscription:"${subscription.name}"`
|
||||
: `in:following rss:"${subscription.url ?? ''}"`,
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'folder',
|
||||
name: 'Saved Searches',
|
||||
section: 'library',
|
||||
children: savedSearches.map((search) => {
|
||||
return {
|
||||
id: search.id,
|
||||
type: 'search',
|
||||
name: search.name,
|
||||
section: 'library',
|
||||
filter: search.filter,
|
||||
}
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
9
packages/db/migrations/0180.do.add_shortcuts_to_user_personalization.sql
Executable file
9
packages/db/migrations/0180.do.add_shortcuts_to_user_personalization.sql
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
-- Type: DO
|
||||
-- Name: add_shortcuts_to_user_personalization
|
||||
-- Description: Add a new shortcuts column to the user personalization table
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.user_personalization ADD COLUMN shortcuts JSONB DEFAULT NULL;
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-- Type: UNDO
|
||||
-- Name: add_shortcuts_to_user_personalization
|
||||
-- Description: Add a new shortcuts column to the user personalization table
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.user_personalization DROP COLUMN shortcuts ;
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -3,7 +3,7 @@ import {
|
|||
Desktop,
|
||||
DeviceTabletSpeaker,
|
||||
DeviceMobileCamera,
|
||||
} from 'phosphor-react'
|
||||
} from '@phosphor-icons/react'
|
||||
import { Box, HStack } from './LayoutPrimitives'
|
||||
import { StyledText, StyledAnchor } from './StyledText'
|
||||
|
||||
|
|
|
|||
|
|
@ -438,6 +438,13 @@ export const Button = styled('button', {
|
|||
borderRadius: '5px',
|
||||
'&:hover': { bg: '$readerHoverBg', opacity: '1' },
|
||||
},
|
||||
menuAction: {
|
||||
display: 'flex',
|
||||
border: 'none',
|
||||
bg: 'transparent',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { bg: 'transparent', opacity: '1' },
|
||||
},
|
||||
themeSwitch: {
|
||||
p: '0px',
|
||||
m: '0px',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { X } from 'phosphor-react'
|
||||
import { X } from '@phosphor-icons/react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from './Button'
|
||||
import { Box } from './LayoutPrimitives'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { HStack, SpanBox, VStack } from './LayoutPrimitives'
|
||||
import { StyledText } from './StyledText'
|
||||
import { NewspaperClipping } from 'phosphor-react'
|
||||
import { NewspaperClipping } from '@phosphor-icons/react'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button } from './Button'
|
||||
import { SpanBox, HStack } from './LayoutPrimitives'
|
||||
import { Circle, X } from 'phosphor-react'
|
||||
import { Circle, X } from '@phosphor-icons/react'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button } from './Button'
|
||||
import { SpanBox, HStack } from './LayoutPrimitives'
|
||||
import { Circle, X } from 'phosphor-react'
|
||||
import { Circle, X } from '@phosphor-icons/react'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
import { Label } from '../../lib/networking/fragments/labelFragment'
|
||||
import { useMemo } from 'react'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { theme } from '../tokens/stitches.config'
|
|||
import { Button } from './Button'
|
||||
import { CloseIcon } from './icons/CloseIcon'
|
||||
import { HelpfulSlothImage } from './images/HelpfulSlothImage'
|
||||
import { ArrowSquareOut } from 'phosphor-react'
|
||||
import { ArrowSquareOut } from '@phosphor-icons/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type FeatureHelpBoxProps = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
Desktop,
|
||||
DeviceTabletSpeaker,
|
||||
DeviceMobileCamera,
|
||||
} from 'phosphor-react'
|
||||
} from '@phosphor-icons/react'
|
||||
import { Box, HStack } from './LayoutPrimitives'
|
||||
import { StyledText, StyledAnchor } from './StyledText'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { SpanBox, HStack } from './LayoutPrimitives'
|
||||
import { Circle, X } from 'phosphor-react'
|
||||
import { Circle, X } from '@phosphor-icons/react'
|
||||
|
||||
type LabelChipProps = {
|
||||
text: string
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DotsThreeVertical } from 'phosphor-react'
|
||||
import { DotsThreeVertical } from '@phosphor-icons/react'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { Box } from './LayoutPrimitives'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { SpanBox, HStack } from './LayoutPrimitives'
|
||||
import { Circle, X } from 'phosphor-react'
|
||||
import { Circle, X } from '@phosphor-icons/react'
|
||||
|
||||
type LabelChipProps = {
|
||||
text: string
|
||||
|
|
|
|||
|
|
@ -40,14 +40,16 @@ const InternalOrExternalLink = (props: InternalOrExternalLinkProps) => {
|
|||
}}
|
||||
>
|
||||
{!isExternal ? (
|
||||
<Link href={props.link} legacyBehavior>{props.children}</Link>
|
||||
<Link href={props.link} legacyBehavior>
|
||||
{props.children}
|
||||
</Link>
|
||||
) : (
|
||||
<a href={props.link} target="_blank" rel="noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
)}
|
||||
</SpanBox>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export const SuggestionBox = (props: SuggestionBoxProps) => {
|
||||
|
|
@ -59,7 +61,7 @@ export const SuggestionBox = (props: SuggestionBoxProps) => {
|
|||
flexDirection: props.size == 'large' ? 'column' : 'row',
|
||||
width: 'fit-content',
|
||||
borderRadius: '5px',
|
||||
background: props.background ?? '$thBackground3',
|
||||
background: props.background ?? 'unset',
|
||||
fontSize: '15px',
|
||||
fontFamily: '$inter',
|
||||
fontWeight: '500',
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
Td,
|
||||
} from 'react-super-responsive-table'
|
||||
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'
|
||||
import { PencilSimple, Plus, Trash } from 'phosphor-react'
|
||||
import { PencilSimple, Plus, Trash } from '@phosphor-icons/react'
|
||||
import { Box, SpanBox, VStack } from './LayoutPrimitives'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { StyledText } from './StyledText'
|
||||
|
|
|
|||
|
|
@ -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 ArchiveSectionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="23"
|
||||
height="23"
|
||||
viewBox="0 0 23 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M19.1667 3.18359H3.83341C2.77487 3.18359 1.91675 4.04171 1.91675 5.10026C1.91675 6.15881 2.77487 7.01693 3.83341 7.01693H19.1667C20.2253 7.01693 21.0834 6.15881 21.0834 5.10026C21.0834 4.04171 20.2253 3.18359 19.1667 3.18359Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M18.2083 8.93359C18.6999 8.93359 19.1053 9.3773 19.1599 9.94943L19.1666 10.0836V16.9836C19.1666 18.8207 17.9696 20.3224 16.4603 20.4278L16.2916 20.4336H6.70825C5.17684 20.4336 3.92525 18.9971 3.83804 17.1868L3.83325 16.9836V10.0836C3.83325 9.44822 4.26259 8.93359 4.79159 8.93359H18.2083ZM13.4166 10.8503H9.58325L9.47113 10.857C9.2382 10.8847 9.02353 10.9968 8.86777 11.1722C8.71201 11.3476 8.62599 11.574 8.62599 11.8086C8.62599 12.0432 8.71201 12.2696 8.86777 12.445C9.02353 12.6203 9.2382 12.7325 9.47113 12.7602L9.58325 12.7669H13.4166L13.5287 12.7602C13.7616 12.7325 13.9763 12.6203 14.1321 12.445C14.2878 12.2696 14.3739 12.0432 14.3739 11.8086C14.3739 11.574 14.2878 11.3476 14.1321 11.1722C13.9763 10.9968 13.7616 10.8847 13.5287 10.857L13.4166 10.8503Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable functional/no-class */
|
||||
/* eslint-disable functional/no-this-expression */
|
||||
import { SpanBox } from '../LayoutPrimitives'
|
||||
import { IconProps } from './IconProps'
|
||||
|
||||
import React from 'react'
|
||||
|
|
@ -19,7 +20,7 @@ export class HomeIcon extends React.Component<IconProps> {
|
|||
>
|
||||
<g>
|
||||
<path
|
||||
d="M19.9329 12.3418L12.7458 6.07813C12.3924 5.77033 11.8662 5.77093 11.514 6.07933L4.35753 12.3433C3.70713 12.9127 4.11003 13.984 4.97433 13.984C5.49123 13.984 5.91033 14.4031 5.91033 14.9203V20.3893C5.91033 20.9065 6.32973 21.3253 6.84663 21.3253H10.1271V16.5322C10.1271 16.2397 10.3638 16.003 10.656 16.003H13.6359C13.9281 16.003 14.1651 16.2397 14.1651 16.5322V21.3256H17.2806C17.7978 21.3256 18.2169 20.9068 18.2169 20.3896V14.9206C18.2169 14.4034 18.636 13.9843 19.1529 13.9843H19.3179C20.1831 13.9837 20.5854 12.9103 19.9329 12.3418Z"
|
||||
d="M19.9329 11.8028L12.7458 5.53907C12.3924 5.23127 11.8662 5.23187 11.514 5.54027L4.35753 11.8043C3.70713 12.3737 4.11003 13.445 4.97433 13.445C5.49123 13.445 5.91033 13.8641 5.91033 14.3813V19.8503C5.91033 20.3675 6.32973 20.7863 6.84663 20.7863H10.1271V15.9932C10.1271 15.7007 10.3638 15.464 10.656 15.464H13.6359C13.9281 15.464 14.1651 15.7007 14.1651 15.9932V20.7866H17.2806C17.7978 20.7866 18.2169 20.3678 18.2169 19.8506V14.3816C18.2169 13.8644 18.636 13.4453 19.1529 13.4453H19.3179C20.1831 13.4447 20.5854 12.3713 19.9329 11.8028Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
|
|
|
|||
31
packages/web/components/elements/icons/NavMoreButtonDown.tsx
Normal file
31
packages/web/components/elements/icons/NavMoreButtonDown.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* eslint-disable functional/no-class */
|
||||
/* eslint-disable functional/no-this-expression */
|
||||
import { IconProps } from './IconProps'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export class NavMoreButtonDownIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M6.25 9.43359L12 15.1836L17.75 9.43359"
|
||||
stroke={color}
|
||||
strokeWidth="1.75"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
29
packages/web/components/elements/icons/NavMoreButtonUp.tsx
Normal file
29
packages/web/components/elements/icons/NavMoreButtonUp.tsx
Normal file
|
|
@ -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 NavMoreButtonUpIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.75 15.1836L12 9.43359L6.25 15.1836"
|
||||
stroke={color}
|
||||
strokeWidth="1.75"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ShortcutFolderClosed extends React.Component<IconProps> {
|
||||
render() {
|
||||
const size = (this.props.size || 26).toString()
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="26"
|
||||
height="26"
|
||||
viewBox="0 0 26 26"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M9.48478 3.43359C9.71374 3.43357 9.93632 3.50899 10.1181 3.64818L10.2223 3.7388L13.041 6.55859H19.9014C20.6985 6.55855 21.4655 6.8631 22.0455 7.40993C22.6254 7.95676 22.9745 8.70454 23.0212 9.50026L23.0265 9.68359V18.0169C23.0265 18.814 22.7219 19.581 22.1751 20.1609C21.6283 20.7409 20.8805 21.09 20.0848 21.1367L19.9014 21.1419H5.31812C4.52102 21.142 3.75404 20.8374 3.17409 20.2906C2.59415 19.7438 2.24509 18.996 2.19832 18.2003L2.19312 18.0169V6.55859C2.19307 5.7615 2.49762 4.99452 3.04445 4.41457C3.59128 3.83463 4.33906 3.48557 5.13478 3.4388L5.31812 3.43359H9.48478Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/* eslint-disable functional/no-class */
|
||||
/* eslint-disable functional/no-this-expression */
|
||||
import { IconProps } from './IconProps'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export class ShortcutFolderOpen extends React.Component<IconProps> {
|
||||
render() {
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="26"
|
||||
height="26"
|
||||
viewBox="0 0 26 26"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_11426_11319)">
|
||||
<path
|
||||
d="M5.24447 20.4395L8.11634 12.7822C8.19075 12.5836 8.32397 12.4126 8.49819 12.2918C8.67241 12.171 8.87934 12.1062 9.09134 12.1061H21.9111M5.24447 20.4395H19.8549C20.3395 20.4393 20.809 20.2702 21.1824 19.9613C21.5558 19.6523 21.8098 19.2228 21.9007 18.7467L22.9382 13.3186C22.963 13.1695 22.955 13.0167 22.9149 12.8709C22.8747 12.7251 22.8032 12.5899 22.7055 12.4745C22.6077 12.3591 22.4861 12.2664 22.3489 12.2028C22.2117 12.1392 22.0623 12.1062 21.9111 12.1061M5.24447 20.4395C4.69193 20.4395 4.16203 20.22 3.77133 19.8293C3.38063 19.4386 3.16113 18.9087 3.16113 18.3561V6.89779C3.16113 6.34525 3.38063 5.81535 3.77133 5.42465C4.16203 5.03395 4.69193 4.81445 5.24447 4.81445H9.41113L12.5361 7.93945H19.8278C20.3803 7.93945 20.9102 8.15895 21.3009 8.54965C21.6916 8.94035 21.9111 9.47025 21.9111 10.0228V12.1061"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
32
packages/web/components/elements/icons/TrashSectionIcon.tsx
Normal file
32
packages/web/components/elements/icons/TrashSectionIcon.tsx
Normal file
|
|
@ -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 TrashSectionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M20.0001 6.30859C20.255 6.30888 20.5001 6.40647 20.6855 6.58144C20.8708 6.75641 20.9823 6.99555 20.9973 7.24999C21.0122 7.50443 20.9294 7.75497 20.7658 7.95043C20.6023 8.14588 20.3702 8.27149 20.1171 8.30159L20.0001 8.30859H19.9191L19.0001 19.3086C19.0002 20.0738 18.7078 20.8101 18.1828 21.3669C17.6579 21.9236 16.94 22.2587 16.1761 22.3036L16.0001 22.3086H8.00011C6.40211 22.3086 5.09611 21.0596 5.00811 19.5586L5.00311 19.3916L4.08011 8.30859H4.00011C3.74523 8.30831 3.50008 8.21071 3.31474 8.03575C3.12941 7.86078 3.01788 7.62164 3.00294 7.3672C2.988 7.11276 3.07079 6.86221 3.23438 6.66676C3.39797 6.47131 3.63002 6.3457 3.88311 6.31559L4.00011 6.30859H20.0001Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M14 2.30859C14.5304 2.30859 15.0391 2.51931 15.4142 2.89438C15.7893 3.26945 16 3.77816 16 4.30859C15.9997 4.56347 15.9021 4.80863 15.7272 4.99396C15.5522 5.1793 15.313 5.29083 15.0586 5.30577C14.8042 5.3207 14.5536 5.23792 14.3582 5.07433C14.1627 4.91074 14.0371 4.67869 14.007 4.42559L14 4.30859H10L9.993 4.42559C9.9629 4.67869 9.83729 4.91074 9.64183 5.07433C9.44638 5.23792 9.19584 5.3207 8.94139 5.30577C8.68695 5.29083 8.44782 5.1793 8.27285 4.99396C8.09788 4.80863 8.00028 4.56347 8 4.30859C7.99984 3.80402 8.19041 3.31803 8.5335 2.94805C8.87659 2.57806 9.34685 2.35144 9.85 2.31359L10 2.30859H14Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import React from 'react'
|
|||
export class AddToLibraryActionIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const strokeColor = (this.props.color || '#D9D9D9').toString()
|
||||
const backgroundColor = (this.props.color || '#3D3D3D').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DotsThree, DotsThreeVertical } from 'phosphor-react'
|
||||
import { DotsThree, DotsThreeVertical } from '@phosphor-icons/react'
|
||||
|
||||
type Orientation = 'horizontal' | 'vertical'
|
||||
|
||||
|
|
@ -10,8 +10,8 @@ type MoreOptionsIconProps = {
|
|||
|
||||
export function MoreOptionsIcon(props: MoreOptionsIconProps): JSX.Element {
|
||||
return props.orientation == 'horizontal' ? (
|
||||
<DotsThree size={props.size} color={props.strokeColor}/>
|
||||
) : (
|
||||
<DotsThreeVertical size={props.size} color={props.strokeColor}/>
|
||||
)
|
||||
<DotsThree size={props.size} color={props.strokeColor} />
|
||||
) : (
|
||||
<DotsThreeVertical size={props.size} color={props.strokeColor} />
|
||||
)
|
||||
}
|
||||
|
|
|
|||
319
packages/web/components/nav-containers/highlights.tsx
Normal file
319
packages/web/components/nav-containers/highlights.tsx
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
import { NavigationLayout } from '../templates/NavigationLayout'
|
||||
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
|
||||
import { useFetchMore } from '../../lib/hooks/useFetchMoreScroll'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useGetHighlights } from '../../lib/networking/queries/useGetHighlights'
|
||||
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
import {
|
||||
UserBasicData,
|
||||
useGetViewerQuery,
|
||||
} from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { SetHighlightLabelsModalPresenter } from '../templates/article/SetLabelsModalPresenter'
|
||||
import { TrashIcon } from '../elements/icons/TrashIcon'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { ConfirmationModal } from '../patterns/ConfirmationModal'
|
||||
import { deleteHighlightMutation } from '../../lib/networking/mutations/deleteHighlightMutation'
|
||||
import { LabelChip } from '../elements/LabelChip'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { HighlightHoverActions } from '../patterns/HighlightHoverActions'
|
||||
import {
|
||||
autoUpdate,
|
||||
offset,
|
||||
size,
|
||||
useFloating,
|
||||
useHover,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react'
|
||||
import { highlightColor } from '../../lib/themeUpdater'
|
||||
|
||||
import { HighlightViewNote } from '../patterns/HighlightNotes'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export function HighlightsContainer(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const viewer = useGetViewerQuery()
|
||||
const [showFilterMenu, setShowFilterMenu] = useState(false)
|
||||
const [_, setShowAddLinkModal] = useState(false)
|
||||
|
||||
const { isLoading, setSize, size, data, mutate } = useGetHighlights({
|
||||
first: PAGE_SIZE,
|
||||
})
|
||||
|
||||
const hasMore = useMemo(() => {
|
||||
if (!data) {
|
||||
return false
|
||||
}
|
||||
return data[data.length - 1].highlights.pageInfo.hasNextPage
|
||||
}, [data])
|
||||
|
||||
const handleFetchMore = useCallback(() => {
|
||||
if (isLoading || !hasMore) {
|
||||
return
|
||||
}
|
||||
setSize(size + 1)
|
||||
}, [isLoading, hasMore, setSize, size])
|
||||
|
||||
useFetchMore(handleFetchMore)
|
||||
|
||||
const highlights = useMemo(() => {
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
return data.flatMap((res) => res.highlights.edges.map((edge) => edge.node))
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
maxWidth: '70%',
|
||||
padding: '20px',
|
||||
margin: '30px 50px 0 0',
|
||||
}}
|
||||
>
|
||||
{highlights.map((highlight) => {
|
||||
return (
|
||||
viewer.viewerData?.me && (
|
||||
<HighlightCard
|
||||
key={highlight.id}
|
||||
highlight={highlight}
|
||||
viewer={viewer.viewerData.me}
|
||||
router={router}
|
||||
mutate={mutate}
|
||||
/>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
type HighlightCardProps = {
|
||||
highlight: Highlight
|
||||
viewer: UserBasicData
|
||||
router: NextRouter
|
||||
mutate: () => void
|
||||
}
|
||||
|
||||
type HighlightAnnotationProps = {
|
||||
highlight: Highlight
|
||||
}
|
||||
|
||||
function HighlightAnnotation({
|
||||
highlight,
|
||||
}: HighlightAnnotationProps): JSX.Element {
|
||||
const [noteMode, setNoteMode] = useState<'edit' | 'preview'>('preview')
|
||||
const [annotation, setAnnotation] = useState(highlight.annotation)
|
||||
|
||||
return (
|
||||
<HighlightViewNote
|
||||
targetId={highlight.id}
|
||||
text={annotation}
|
||||
placeHolder="Add notes to this highlight..."
|
||||
highlight={highlight}
|
||||
mode={noteMode}
|
||||
setEditMode={setNoteMode}
|
||||
updateHighlight={(highlight) => {
|
||||
setAnnotation(highlight.annotation)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function HighlightCard(props: HighlightCardProps): JSX.Element {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
|
||||
useState<undefined | string>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId: string) => {
|
||||
const router = props.router
|
||||
const viewer = props.viewer
|
||||
const item = props.highlight.libraryItem
|
||||
|
||||
if (!router || !router.isReady || !viewer || !item) {
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
}
|
||||
|
||||
router.push(
|
||||
{
|
||||
pathname: '/[username]/[slug]',
|
||||
query: {
|
||||
username: viewer.profile.username,
|
||||
slug: item.slug,
|
||||
},
|
||||
hash: highlightId,
|
||||
},
|
||||
`${viewer.profile.username}/${item.slug}#${highlightId}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
)
|
||||
},
|
||||
[props.highlight.libraryItem, props.viewer, props.router]
|
||||
)
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
middleware: [
|
||||
offset({
|
||||
mainAxis: -25,
|
||||
}),
|
||||
size(),
|
||||
],
|
||||
placement: 'top-end',
|
||||
whileElementsMounted: autoUpdate,
|
||||
})
|
||||
|
||||
const hover = useHover(context)
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
|
||||
|
||||
return (
|
||||
<VStack
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps()}
|
||||
css={{
|
||||
width: '100%',
|
||||
fontFamily: '$inter',
|
||||
padding: '20px',
|
||||
marginBottom: '20px',
|
||||
bg: '$thBackground2',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<HighlightHoverActions
|
||||
viewer={props.viewer}
|
||||
highlight={props.highlight}
|
||||
isHovered={isOpen ?? false}
|
||||
viewInReader={viewInReader}
|
||||
setLabelsTarget={setLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
css={{
|
||||
width: '30px',
|
||||
height: '5px',
|
||||
backgroundColor: highlightColor(props.highlight.color),
|
||||
borderRadius: '2px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
css={{
|
||||
color: '$thText',
|
||||
fontSize: '11px',
|
||||
marginTop: '10px',
|
||||
fontWeight: 300,
|
||||
}}
|
||||
>
|
||||
{timeAgo(props.highlight.updatedAt)}
|
||||
</Box>
|
||||
{props.highlight.quote && (
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{props.highlight.quote}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
<HighlightAnnotation highlight={props.highlight} />
|
||||
{props.highlight.labels && (
|
||||
<HStack
|
||||
css={{
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
{props.highlight.labels.map((label) => {
|
||||
return (
|
||||
<LabelChip key={label.id} color={label.color} text={label.name} />
|
||||
)
|
||||
})}
|
||||
</HStack>
|
||||
)}
|
||||
<Box
|
||||
css={{
|
||||
color: '$thText',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 300,
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
{props.highlight.libraryItem?.title}
|
||||
</Box>
|
||||
<Box
|
||||
css={{
|
||||
color: '$grayText',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 300,
|
||||
}}
|
||||
>
|
||||
{props.highlight.libraryItem?.author}
|
||||
</Box>
|
||||
{showConfirmDeleteHighlightId && (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={() => {
|
||||
;(async () => {
|
||||
const highlightId = showConfirmDeleteHighlightId
|
||||
const success = await deleteHighlightMutation(
|
||||
props.highlight.libraryItem?.id || '',
|
||||
showConfirmDeleteHighlightId
|
||||
)
|
||||
props.mutate()
|
||||
if (success) {
|
||||
showSuccessToast('Highlight deleted.', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
const event = new CustomEvent('deleteHighlightbyId', {
|
||||
detail: highlightId,
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
} else {
|
||||
showErrorToast('Error deleting highlight', {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
}
|
||||
})()
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
icon={
|
||||
<TrashIcon
|
||||
size={40}
|
||||
color={theme.colors.grayTextContrast.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{labelsTarget && (
|
||||
<SetHighlightLabelsModalPresenter
|
||||
highlight={labelsTarget}
|
||||
highlightId={labelsTarget.id}
|
||||
onUpdate={(highlight) => {
|
||||
// Don't actually need to do something here
|
||||
console.log('update highlight: ', highlight)
|
||||
}}
|
||||
onOpenChange={() => {
|
||||
props.mutate()
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import * as HoverCard from '@radix-ui/react-hover-card'
|
||||
import { styled } from '@stitches/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import { AddToLibraryActionIcon } from '../../components/elements/icons/home/AddToLibraryActionIcon'
|
||||
import { ArchiveActionIcon } from '../../components/elements/icons/home/ArchiveActionIcon'
|
||||
import { CommentActionIcon } from '../../components/elements/icons/home/CommentActionIcon'
|
||||
import { RemoveActionIcon } from '../../components/elements/icons/home/RemoveActionIcon'
|
||||
import { ShareActionIcon } from '../../components/elements/icons/home/ShareActionIcon'
|
||||
import Pagination from '../../components/elements/Pagination'
|
||||
import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
|
||||
import { theme } from '../../components/tokens/stitches.config'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Button } from '../elements/Button'
|
||||
import { AddToLibraryActionIcon } from '../elements/icons/home/AddToLibraryActionIcon'
|
||||
import { ArchiveActionIcon } from '../elements/icons/home/ArchiveActionIcon'
|
||||
import { CommentActionIcon } from '../elements/icons/home/CommentActionIcon'
|
||||
import { RemoveActionIcon } from '../elements/icons/home/RemoveActionIcon'
|
||||
import { ShareActionIcon } from '../elements/icons/home/ShareActionIcon'
|
||||
import Pagination from '../elements/Pagination'
|
||||
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
|
||||
import { useGetHiddenHomeSection } from '../../lib/networking/queries/useGetHiddenHomeSection'
|
||||
import {
|
||||
|
|
@ -24,20 +24,10 @@ import {
|
|||
SubscriptionType,
|
||||
useGetSubscriptionsQuery,
|
||||
} from '../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
SpanBox,
|
||||
VStack,
|
||||
} from './../../components/elements/LayoutPrimitives'
|
||||
import { List, ThumbsDown, ThumbsUp } from 'phosphor-react'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { DEFAULT_HEADER_HEIGHT } from '../../components/templates/homeFeed/HeaderSpacer'
|
||||
import { NavigationMenu } from '../../components/templates/navMenu/NavigationMenu'
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const [showLeftMenu, setShowLeftMenu] = useState(false)
|
||||
export function HomeContainer(): JSX.Element {
|
||||
const homeData = useGetHomeItems()
|
||||
useApplyLocalTheme()
|
||||
|
||||
|
|
@ -53,24 +43,6 @@ export default function Home(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
<Toaster />
|
||||
<Header
|
||||
toggleMenu={() => {
|
||||
setShowLeftMenu(!showLeftMenu)
|
||||
}}
|
||||
/>
|
||||
{showLeftMenu && (
|
||||
<NavigationMenu
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setShowAddLinkModal={() => {}}
|
||||
searchTerm={''}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
applySearchQuery={(searchQuery: string) => {}}
|
||||
showFilterMenu={showLeftMenu}
|
||||
setShowFilterMenu={(show) => {
|
||||
setShowLeftMenu(show)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
|
|
@ -142,12 +114,11 @@ const JustAddedHomeSection = (props: HomeSectionProps): JSX.Element => {
|
|||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
color: '$homeTextTitle',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
</SpanBox>
|
||||
|
||||
{props.homeSection.items.map((homeItem) => {
|
||||
return <JustAddedItemView key={homeItem.id} homeItem={homeItem} />
|
||||
})}
|
||||
|
|
@ -169,7 +140,7 @@ const TopPicksHomeSection = (props: HomeSectionProps): JSX.Element => {
|
|||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
color: '$homeTextTitle',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
|
|
@ -252,7 +223,7 @@ const HiddenHomeSection = (props: HomeSectionProps): JSX.Element => {
|
|||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
color: '$homeTextTitle',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
|
|
@ -319,7 +290,7 @@ const TimeAgo = (props: HomeItemViewProps): JSX.Element => {
|
|||
fontSize: '12px',
|
||||
fontWeight: 'medium',
|
||||
fontFamily: '$inter',
|
||||
color: '$readerTextSubtle',
|
||||
color: '$homeTextSubtle',
|
||||
}}
|
||||
>
|
||||
{timeAgo(props.homeItem.date)}
|
||||
|
|
@ -337,7 +308,7 @@ const Title = (props: HomeItemViewProps): JSX.Element => {
|
|||
lineHeight: '20px',
|
||||
fontWeight: '600',
|
||||
fontFamily: '$inter',
|
||||
color: '$readerText',
|
||||
color: '$homeTextTitle',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
wordBreak: 'break-word',
|
||||
|
|
@ -386,7 +357,7 @@ const JustAddedItemView = (props: HomeItemViewProps): JSX.Element => {
|
|||
padding: '5px',
|
||||
borderRadius: '5px',
|
||||
'&:hover': {
|
||||
bg: '$thBackground',
|
||||
bg: '$homeCardHover',
|
||||
borderRadius: '0px',
|
||||
},
|
||||
}}
|
||||
|
|
@ -423,7 +394,7 @@ const TopicPickHomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
|||
|
||||
borderRadius: '5px',
|
||||
'&:hover': {
|
||||
bg: '#323232',
|
||||
bg: '$homeCardHover',
|
||||
borderRadius: '0px',
|
||||
},
|
||||
}}
|
||||
|
|
@ -470,22 +441,26 @@ const TopicPickHomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
|||
</SpanBox>
|
||||
<HStack css={{ gap: '10px', my: '15px', px: '20px' }}>
|
||||
<Button style="homeAction">
|
||||
<AddToLibraryActionIcon />
|
||||
<AddToLibraryActionIcon
|
||||
color={theme.colors.homeActionIcons.toString()}
|
||||
/>
|
||||
</Button>
|
||||
<Button style="homeAction">
|
||||
<CommentActionIcon />
|
||||
<CommentActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
</Button>
|
||||
<Button style="homeAction">
|
||||
<ShareActionIcon />
|
||||
<ShareActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
</Button>
|
||||
<Button style="homeAction">
|
||||
<ArchiveActionIcon />
|
||||
<ArchiveActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
</Button>
|
||||
<Button style="homeAction">
|
||||
<RemoveActionIcon />
|
||||
<RemoveActionIcon color={theme.colors.homeActionIcons.toString()} />
|
||||
</Button>
|
||||
</HStack>
|
||||
<Box css={{ mt: '15px', width: '100%', height: '1px', bg: '#3D3D3D' }} />
|
||||
<Box
|
||||
css={{ mt: '15px', width: '100%', height: '1px', bg: '$homeDivider' }}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -557,7 +532,7 @@ const SourceInfo = (props: HomeItemViewProps) => (
|
|||
fontFamily: '$inter',
|
||||
fontWeight: '500',
|
||||
fontSize: '13px',
|
||||
color: '$readerFont',
|
||||
color: '$homeTextSource',
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
>
|
||||
|
|
@ -645,7 +620,7 @@ const SubscriptionSourceHoverContent = (
|
|||
css={{
|
||||
fontFamily: '$inter',
|
||||
fontSize: '13px',
|
||||
color: '$thTextSubtle4',
|
||||
color: '$homeTextBody',
|
||||
}}
|
||||
>
|
||||
{subscription ? <>{subscription.description}</> : <></>}
|
||||
|
|
@ -653,144 +628,3 @@ const SubscriptionSourceHoverContent = (
|
|||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
// const SiteSourceHoverContent = (
|
||||
// props: SourceHoverContentProps
|
||||
// ): JSX.Element => {
|
||||
// const sendHomeFeedback = useCallback(
|
||||
// async (feedbackType: SendHomeFeedbackType) => {
|
||||
// const feedback: SendHomeFeedbackInput = {
|
||||
// feedbackType,
|
||||
// }
|
||||
// feedback.site = props.source.name
|
||||
// const result = await sendHomeFeedbackMutation(feedback)
|
||||
// if (result) {
|
||||
// showSuccessToast('Feedback sent')
|
||||
// } else {
|
||||
// showErrorToast('Error sending feedback')
|
||||
// }
|
||||
// },
|
||||
// [props]
|
||||
// )
|
||||
|
||||
// return (
|
||||
// <VStack
|
||||
// alignment="start"
|
||||
// distribution="start"
|
||||
// css={{
|
||||
// width: '240px',
|
||||
// height: '100px',
|
||||
// bg: '$thBackground2',
|
||||
// borderRadius: '10px',
|
||||
// padding: '15px',
|
||||
// gap: '10px',
|
||||
// boxShadow: theme.shadows.cardBoxShadow.toString(),
|
||||
// }}
|
||||
// >
|
||||
// <HStack
|
||||
// distribution="start"
|
||||
// alignment="center"
|
||||
// css={{ width: '100%', gap: '10px' }}
|
||||
// >
|
||||
// {props.source.icon && (
|
||||
// <SiteIcon
|
||||
// src={props.source.icon}
|
||||
// alt={props.source.name}
|
||||
// size="large"
|
||||
// />
|
||||
// )}
|
||||
// <SpanBox
|
||||
// css={{
|
||||
// fontFamily: '$inter',
|
||||
// fontWeight: '500',
|
||||
// fontSize: '14px',
|
||||
// }}
|
||||
// >
|
||||
// {props.source.name}
|
||||
// </SpanBox>
|
||||
// </HStack>
|
||||
// {/* <SpanBox
|
||||
// css={{
|
||||
// fontFamily: '$inter',
|
||||
// fontSize: '13px',
|
||||
// color: '$thTextSubtle4',
|
||||
// }}
|
||||
// >
|
||||
// {subscription ? <>{subscription.description}</> : <></>}
|
||||
// </SpanBox> */}
|
||||
// <FeedbackView sendFeedback={sendHomeFeedback} />
|
||||
// </VStack>
|
||||
// )
|
||||
// }
|
||||
|
||||
// type FeedbackViewProps = {
|
||||
// sendFeedback: (type: SendHomeFeedbackType) => void
|
||||
// }
|
||||
|
||||
// const FeedbackView = (props: FeedbackViewProps): JSX.Element => {
|
||||
// return (
|
||||
// <HStack css={{ ml: 'auto', mt: 'auto', gap: '5px' }}>
|
||||
// <Button
|
||||
// style="plainIcon"
|
||||
// onClick={(event) => {
|
||||
// props.sendFeedback('MORE')
|
||||
// event.preventDefault()
|
||||
// event.stopPropagation()
|
||||
// }}
|
||||
// >
|
||||
// <ThumbsUp weight="fill" />
|
||||
// </Button>
|
||||
// <Button
|
||||
// style="plainIcon"
|
||||
// onClick={(event) => {
|
||||
// props.sendFeedback('LESS')
|
||||
// event.preventDefault()
|
||||
// event.stopPropagation()
|
||||
// }}
|
||||
// >
|
||||
// <ThumbsDown weight="fill" />
|
||||
// </Button>
|
||||
// </HStack>
|
||||
// )
|
||||
// }
|
||||
|
||||
type HeaderProps = {
|
||||
toggleMenu: () => void
|
||||
}
|
||||
|
||||
const Header = (props: HeaderProps): JSX.Element => {
|
||||
const small = false
|
||||
|
||||
return (
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
css={{
|
||||
zIndex: 5,
|
||||
position: 'fixed',
|
||||
left: '15px',
|
||||
top: '15px',
|
||||
height: small ? '60px' : DEFAULT_HEADER_HEIGHT,
|
||||
transition: 'height 0.5s',
|
||||
'@lgDown': { px: '20px' },
|
||||
'@mdDown': {
|
||||
px: '10px',
|
||||
left: '0px',
|
||||
right: '0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack alignment="center" distribution="center">
|
||||
<Button
|
||||
style="plainIcon"
|
||||
onClick={(event) => {
|
||||
props.toggleMenu()
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<List size="25" color={theme.colors.readerTextSubtle.toString()} />
|
||||
</Button>
|
||||
</VStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { isAndroid } from '../../lib/deviceType'
|
|||
import { styled, theme } from '../tokens/stitches.config'
|
||||
import { Button } from '../elements/Button'
|
||||
import { HStack, Box } from '../elements/LayoutPrimitives'
|
||||
import { Circle, CheckCircle } from 'phosphor-react'
|
||||
import { Circle, CheckCircle } from '@phosphor-icons/react'
|
||||
import { LabelIcon } from '../elements/icons/LabelIcon'
|
||||
import { NotebookIcon } from '../elements/icons/NotebookIcon'
|
||||
import { highlightColor, highlightColors } from '../../lib/themeUpdater'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||
import { Box } from '../elements/LayoutPrimitives'
|
||||
import { Button } from '../elements/Button'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { BookOpen, Copy } from 'phosphor-react'
|
||||
import { BookOpen, Copy } from '@phosphor-icons/react'
|
||||
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
|
|
|
|||
|
|
@ -31,4 +31,6 @@ export type LinkedItemCardProps = {
|
|||
|
||||
isHovered?: boolean
|
||||
isLoading?: boolean
|
||||
|
||||
legacyLayout?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
autoUpdate,
|
||||
} from '@floating-ui/react'
|
||||
import { CardMenu } from '../CardMenu'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { DotsThree } from '@phosphor-icons/react'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { LoadingBarOverlay, ProgressBarOverlay } from './LibraryListCard'
|
||||
import { GridFallbackImage } from './FallbackImage'
|
||||
|
|
@ -64,7 +64,7 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
|
|||
css={{
|
||||
pl: '0px',
|
||||
padding: '0px',
|
||||
width: '293px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: '270px',
|
||||
background: 'white',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { CaretDown, CaretUp } from 'phosphor-react'
|
||||
import { CaretDown, CaretUp } from '@phosphor-icons/react'
|
||||
import { MetaStyle, timeAgo, TitleStyle } from './LibraryCardStyles'
|
||||
import { styled } from '@stitches/react'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
|
@ -197,7 +197,9 @@ export function LibraryHighlightGridCard(
|
|||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{`View ${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`}
|
||||
{`View ${highlightCount} highlight${
|
||||
highlightCount > 1 ? 's' : ''
|
||||
}`}
|
||||
<CaretDown
|
||||
size={10}
|
||||
weight="bold"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryIt
|
|||
import { LinkedItemCardAction } from './CardTypes'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { DotsThree, Share } from 'phosphor-react'
|
||||
import { DotsThree, Share } from '@phosphor-icons/react'
|
||||
import { CardMenu } from '../CardMenu'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
autoUpdate,
|
||||
} from '@floating-ui/react'
|
||||
import { CardMenu } from '../CardMenu'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { DotsThree } from '@phosphor-icons/react'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { CoverImage } from '../../elements/CoverImage'
|
||||
import { ProgressBar } from '../../elements/ProgressBar'
|
||||
|
|
@ -54,6 +54,26 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
|||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
|
||||
|
||||
const layoutWidths = props.legacyLayout
|
||||
? {
|
||||
width: '100vw',
|
||||
'@media (min-width: 768px)': {
|
||||
width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`,
|
||||
},
|
||||
'@media (min-width: 930px)': {
|
||||
width: '580px',
|
||||
},
|
||||
'@media (min-width: 1280px)': {
|
||||
width: '890px',
|
||||
},
|
||||
'@media (min-width: 1600px)': {
|
||||
width: '1200px',
|
||||
},
|
||||
}
|
||||
: {
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack
|
||||
ref={refs.setReference}
|
||||
|
|
@ -68,22 +88,10 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
|||
borderStyle: 'none',
|
||||
borderBottom: 'none',
|
||||
borderRadius: '6px',
|
||||
width: '100vw',
|
||||
'@media (min-width: 768px)': {
|
||||
width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`,
|
||||
},
|
||||
'@media (min-width: 930px)': {
|
||||
width: '580px',
|
||||
},
|
||||
'@media (min-width: 1280px)': {
|
||||
width: '890px',
|
||||
},
|
||||
'@media (min-width: 1600px)': {
|
||||
width: '1200px',
|
||||
},
|
||||
'@media (max-width: 930px)': {
|
||||
borderRadius: '0px',
|
||||
},
|
||||
...layoutWidths,
|
||||
}}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { isAndroid } from '../../lib/deviceType'
|
|||
import { styled, theme } from '../tokens/stitches.config'
|
||||
import { Button } from '../elements/Button'
|
||||
import { HStack, Box } from '../elements/LayoutPrimitives'
|
||||
import { Circle, CheckCircle } from 'phosphor-react'
|
||||
import { Circle, CheckCircle } from '@phosphor-icons/react'
|
||||
import { LabelIcon } from '../elements/icons/LabelIcon'
|
||||
import { NotebookIcon } from '../elements/icons/NotebookIcon'
|
||||
import { highlightColor, highlightColors } from '../../lib/themeUpdater'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable functional/no-class */
|
||||
|
||||
import { FloppyDisk } from 'phosphor-react'
|
||||
import { FloppyDisk } from '@phosphor-icons/react'
|
||||
import { PluginComponent } from 'react-markdown-editor-lite'
|
||||
import { Button } from '../elements/Button'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { useCallback, useRef, useState } from 'react'
|
||||
import * as Progress from '@radix-ui/react-progress'
|
||||
import { File, Info } from 'phosphor-react'
|
||||
import { locale, timeZone } from '../../../lib/dateFormatting'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { FormInput } from '../../elements/FormElements'
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { File, Info } from '@phosphor-icons/react'
|
||||
import { locale, timeZone } from '../../lib/dateFormatting'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { Button } from '../elements/Button'
|
||||
import { FormInput } from '../elements/FormElements'
|
||||
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
|
||||
import {
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
ModalRoot,
|
||||
} from '../../elements/ModalPrimitives'
|
||||
import { CloseButton } from '../../elements/CloseButton'
|
||||
} from '../elements/ModalPrimitives'
|
||||
import { CloseButton } from '../elements/CloseButton'
|
||||
import { styled } from '@stitches/react'
|
||||
import Dropzone, {
|
||||
Accept,
|
||||
|
|
@ -20,17 +20,17 @@ import Dropzone, {
|
|||
FileRejection,
|
||||
} from 'react-dropzone'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { validateCsvFile } from '../../../utils/csvValidator'
|
||||
import { validateCsvFile } from '../../utils/csvValidator'
|
||||
import {
|
||||
uploadImportFileRequestMutation,
|
||||
UploadImportFileType,
|
||||
} from '../../../lib/networking/mutations/uploadImportFileMutation'
|
||||
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
|
||||
} from '../../lib/networking/mutations/uploadImportFileMutation'
|
||||
import { uploadFileRequestMutation } from '../../lib/networking/mutations/uploadFileMutation'
|
||||
import axios from 'axios'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { formatMessage } from '../../../locales/en/messages'
|
||||
import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation'
|
||||
import { SubscriptionType } from '../../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { formatMessage } from '../../locales/en/messages'
|
||||
import { subscribeMutation } from '../../lib/networking/mutations/subscribeMutation'
|
||||
import { SubscriptionType } from '../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
|
||||
type TabName = 'link' | 'feed' | 'opml' | 'pdf' | 'import'
|
||||
|
||||
196
packages/web/components/templates/NavigationLayout.tsx
Normal file
196
packages/web/components/templates/NavigationLayout.tsx
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import { PageMetaData, PageMetaDataProps } from '../patterns/PageMetaData'
|
||||
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
|
||||
import { ReactNode, useEffect, useState, useCallback } from 'react'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
import { ConfirmationModal } from '../patterns/ConfirmationModal'
|
||||
import { KeyboardShortcutListModal } from './KeyboardShortcutListModal'
|
||||
import { setupAnalytics } from '../../lib/analytics'
|
||||
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { logout } from '../../lib/logout'
|
||||
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
|
||||
import { updateTheme } from '../../lib/themeUpdater'
|
||||
import { Priority, useRegisterActions } from 'kbar'
|
||||
import { ThemeId, theme } from '../tokens/stitches.config'
|
||||
import { NavigationMenu } from './navMenu/NavigationMenu'
|
||||
import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer'
|
||||
import { Button } from '../elements/Button'
|
||||
import { List } from '@phosphor-icons/react'
|
||||
import { usePersistedState } from '../../lib/hooks/usePersistedState'
|
||||
import 'allotment/dist/style.css'
|
||||
import { LibrarySideBar } from './library/LibrarySideBar'
|
||||
|
||||
export type NavigationSection =
|
||||
| 'home'
|
||||
| 'library'
|
||||
| 'subscriptions'
|
||||
| 'highlights'
|
||||
| 'archive'
|
||||
| 'trash'
|
||||
|
||||
type NavigationLayoutProps = {
|
||||
children: ReactNode
|
||||
rightPane?: ReactNode
|
||||
section: NavigationSection
|
||||
pageMetaDataProps?: PageMetaDataProps
|
||||
}
|
||||
|
||||
export function NavigationLayout(props: NavigationLayoutProps): JSX.Element {
|
||||
useApplyLocalTheme()
|
||||
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const router = useRouter()
|
||||
const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false)
|
||||
const [showKeyboardCommandsModal, setShowKeyboardCommandsModal] =
|
||||
useState(false)
|
||||
|
||||
const [showNavMenu, setShowNavMenu] = usePersistedState<boolean>({
|
||||
key: 'nav-show-menu',
|
||||
isSessionStorage: false,
|
||||
initialValue: true,
|
||||
})
|
||||
|
||||
useKeyboardShortcuts(navigationCommands(router))
|
||||
|
||||
useKeyboardShortcuts(
|
||||
primaryCommands((action) => {
|
||||
switch (action) {
|
||||
case 'toggleShortcutHelpModalDisplay':
|
||||
setShowKeyboardCommandsModal(true)
|
||||
break
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
useRegisterActions(
|
||||
[
|
||||
{
|
||||
id: 'home',
|
||||
section: 'Navigation',
|
||||
name: 'Go to Home (Library) ',
|
||||
shortcut: ['g h'],
|
||||
keywords: 'go home',
|
||||
perform: () => router?.push('/home'),
|
||||
},
|
||||
{
|
||||
id: 'lightTheme',
|
||||
section: 'Preferences',
|
||||
name: 'Change theme (light) ',
|
||||
shortcut: ['v', 'l'],
|
||||
keywords: 'light theme',
|
||||
priority: Priority.LOW,
|
||||
perform: () => updateTheme(ThemeId.Light),
|
||||
},
|
||||
{
|
||||
id: 'darkTheme',
|
||||
section: 'Preferences',
|
||||
name: 'Change theme (dark) ',
|
||||
shortcut: ['v', 'd'],
|
||||
keywords: 'dark theme',
|
||||
priority: Priority.LOW,
|
||||
perform: () => updateTheme(ThemeId.Dark),
|
||||
},
|
||||
],
|
||||
[router]
|
||||
)
|
||||
|
||||
// Attempt to identify the user if they are logged in.
|
||||
useEffect(() => {
|
||||
setupAnalytics(viewerData?.me)
|
||||
}, [viewerData?.me])
|
||||
|
||||
const showLogout = useCallback(() => {
|
||||
setShowLogoutConfirmation(true)
|
||||
}, [setShowLogoutConfirmation])
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('logout', showLogout)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('logout', showLogout)
|
||||
}
|
||||
}, [showLogout])
|
||||
|
||||
return (
|
||||
<HStack
|
||||
css={{ width: '100vw', height: '100vh' }}
|
||||
distribution="start"
|
||||
alignment="start"
|
||||
>
|
||||
{props.pageMetaDataProps ? (
|
||||
<PageMetaData {...props.pageMetaDataProps} />
|
||||
) : null}
|
||||
<Header
|
||||
toggleMenu={() => {
|
||||
setShowNavMenu(!showNavMenu)
|
||||
}}
|
||||
/>
|
||||
{showNavMenu && (
|
||||
<NavigationMenu
|
||||
section={props.section}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setShowAddLinkModal={() => {}}
|
||||
showMenu={showNavMenu}
|
||||
setShowMenu={setShowNavMenu}
|
||||
/>
|
||||
)}
|
||||
{props.children}
|
||||
{showLogoutConfirmation ? (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to log out?'}
|
||||
onAccept={logout}
|
||||
onOpenChange={() => setShowLogoutConfirmation(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showKeyboardCommandsModal ? (
|
||||
<KeyboardShortcutListModal
|
||||
onOpenChange={() => setShowKeyboardCommandsModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
toggleMenu: () => void
|
||||
}
|
||||
|
||||
const Header = (props: HeaderProps): JSX.Element => {
|
||||
const small = false
|
||||
|
||||
return (
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
css={{
|
||||
zIndex: 5,
|
||||
position: 'fixed',
|
||||
left: '15px',
|
||||
top: '15px',
|
||||
height: small ? '60px' : DEFAULT_HEADER_HEIGHT,
|
||||
transition: 'height 0.5s',
|
||||
'@lgDown': { px: '20px' },
|
||||
'@mdDown': {
|
||||
px: '10px',
|
||||
left: '0px',
|
||||
right: '0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack alignment="center" distribution="center">
|
||||
<Button
|
||||
style="plainIcon"
|
||||
onClick={(event) => {
|
||||
props.toggleMenu()
|
||||
event.preventDefault()
|
||||
}}
|
||||
css={{ height: 'unset' }}
|
||||
>
|
||||
<List size="25" color={theme.colors.readerTextSubtle.toString()} />
|
||||
</Button>
|
||||
</VStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { Moon, Sun } from 'phosphor-react'
|
||||
import { Moon, Sun } from '@phosphor-icons/react'
|
||||
import { ReactNode, useCallback } from 'react'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { Avatar } from '../elements/Avatar'
|
||||
|
|
@ -20,8 +20,6 @@ import { ThemeSelector } from './article/ReaderSettingsControl'
|
|||
|
||||
type PrimaryDropdownProps = {
|
||||
children?: ReactNode
|
||||
showThemeSection: boolean
|
||||
showFullThemeSection: boolean
|
||||
|
||||
layout?: LayoutType
|
||||
updateLayout?: (layout: LayoutType) => void
|
||||
|
|
@ -194,8 +192,7 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element {
|
|||
</VStack>
|
||||
</HStack>
|
||||
<DropdownSeparator />
|
||||
{props.showThemeSection && <LegacyMenuThemeSection {...props} />}
|
||||
{props.showFullThemeSection && <ThemeSection {...props} />}
|
||||
<ThemeSection {...props} />
|
||||
<DropdownOption
|
||||
onSelect={() => headerDropdownActionHandler('navigate-to-install')}
|
||||
title="Install"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { SettingsMenu } from './navMenu/SettingsMenu'
|
|||
import { SettingsDropdown } from './navMenu/SettingsDropdown'
|
||||
import { useVerifyAuth } from '../../lib/hooks/useVerifyAuth'
|
||||
import Link from 'next/link'
|
||||
import { CaretLeft } from 'phosphor-react'
|
||||
import { CaretLeft } from '@phosphor-icons/react'
|
||||
|
||||
type SettingsLayoutProps = {
|
||||
title?: string
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as Progress from '@radix-ui/react-progress'
|
||||
import { styled } from '@stitches/react'
|
||||
import axios from 'axios'
|
||||
import { File } from 'phosphor-react'
|
||||
import { File } from '@phosphor-icons/react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import Dropzone, { DropEvent, DropzoneRef, FileRejection } from 'react-dropzone'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
|
|
|||
|
|
@ -67,8 +67,9 @@ export function Article(props: ArticleProps): JSX.Element {
|
|||
const [lightboxOpen, setLightboxOpen] = useState(false)
|
||||
const [imageSrcs, setImageSrcs] = useState<SlideImage[]>([])
|
||||
const [lightboxIndex, setlightBoxIndex] = useState(0)
|
||||
const [linkHoverData, setlinkHoverData] =
|
||||
useState<LinkHoverData | undefined>()
|
||||
const [linkHoverData, setlinkHoverData] = useState<
|
||||
LinkHoverData | undefined
|
||||
>()
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
|
|
@ -97,7 +98,10 @@ export function Article(props: ArticleProps): JSX.Element {
|
|||
// Post message to webkit so apple app embeds get progress updates
|
||||
// TODO: verify if ios still needs this code...seeems to be duplicated
|
||||
useEffect(() => {
|
||||
if (typeof window?.webkit != 'undefined') {
|
||||
if (
|
||||
typeof window?.webkit != 'undefined' &&
|
||||
'messageHandlers' in window.webkit
|
||||
) {
|
||||
window.webkit.messageHandlers.readingProgressUpdate?.postMessage({
|
||||
progress: readingProgress,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -80,13 +80,15 @@ 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<
|
||||
Highlight | undefined
|
||||
>(undefined)
|
||||
|
||||
const [selectionData, setSelectionData] = useSelection(highlightLocations)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [
|
||||
confirmDeleteHighlightWithNoteId,
|
||||
|
|
@ -363,13 +365,15 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
// highlight, so the app can display a native menu
|
||||
const rect = (target as Element).getBoundingClientRect()
|
||||
|
||||
window?.webkit?.messageHandlers.viewerAction?.postMessage({
|
||||
actionID: 'showMenu',
|
||||
rectX: rect.x,
|
||||
rectY: rect.y,
|
||||
rectWidth: rect.width,
|
||||
rectHeight: rect.height,
|
||||
})
|
||||
if (window?.webkit?.messageHandlers) {
|
||||
window?.webkit?.messageHandlers.viewerAction?.postMessage({
|
||||
actionID: 'showMenu',
|
||||
rectX: rect.x,
|
||||
rectY: rect.y,
|
||||
rectWidth: rect.width,
|
||||
rectHeight: rect.height,
|
||||
})
|
||||
}
|
||||
|
||||
window?.AndroidWebKitMessenger?.handleIdentifiableMessage(
|
||||
'existingHighlightTap',
|
||||
|
|
@ -394,7 +398,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
const highlight = highlights.find(($0) => $0.id === id)
|
||||
setFocusedHighlight(highlight)
|
||||
setLabelsTarget(highlight)
|
||||
} else {
|
||||
} else if (window?.webkit?.messageHandlers) {
|
||||
window?.webkit?.messageHandlers.viewerAction?.postMessage({
|
||||
actionID: 'pageTapped',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { HStack } from '../../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { Sidebar } from 'phosphor-react'
|
||||
import { Sidebar } from '@phosphor-icons/react'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { ExportIcon } from '../../elements/icons/ExportIcon'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { StyledText } from '../../elements/StyledText'
|
|||
import { theme } from '../../tokens/stitches.config'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { X } from 'phosphor-react'
|
||||
import { X } from '@phosphor-icons/react'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { diff_match_patch } from 'diff-match-patch'
|
||||
|
|
@ -37,11 +37,13 @@ export const getHighlightLocation = (patch: string): number | undefined => {
|
|||
|
||||
export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
|
||||
const [allAnnotations, setAllAnnotations] =
|
||||
useState<Highlight[] | undefined>(undefined)
|
||||
const [allAnnotations, setAllAnnotations] = useState<Highlight[] | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [deletedHighlights, setDeletedAnnotations] =
|
||||
useState<Highlight[] | undefined>(undefined)
|
||||
const [deletedHighlights, setDeletedAnnotations] = useState<
|
||||
Highlight[] | undefined
|
||||
>(undefined)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
props.onClose(allAnnotations ?? [], deletedHighlights ?? [])
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
CaretLeft,
|
||||
CaretRight,
|
||||
Check,
|
||||
} from 'phosphor-react'
|
||||
} from '@phosphor-icons/react'
|
||||
import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
|
||||
import { showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { ReaderSettings } from '../../../lib/hooks/useReaderSettings'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { StyledText } from '../../elements/StyledText'
|
|||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
|
||||
import { Check, Circle, Plus, WarningCircle } from 'phosphor-react'
|
||||
import { Check, Circle, Plus, WarningCircle } from '@phosphor-icons/react'
|
||||
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { useCopyLink } from '../../../lib/hooks/useCopyLink'
|
|||
import { CloseIcon } from '../../elements/images/CloseIcon'
|
||||
import { OmnivoreLogoIcon } from '../../elements/images/OmnivoreNameLogo'
|
||||
import { useState } from 'react'
|
||||
import { TwitterLogo, FacebookLogo } from 'phosphor-react'
|
||||
import { TwitterLogo, FacebookLogo } from '@phosphor-icons/react'
|
||||
|
||||
type ShareType = 'link' | 'highlight'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
|
|||
import { LibraryFilterMenu } from '../navMenu/LibraryMenu'
|
||||
import { DiscoverHeader } from './DiscoverHeader/DiscoverHeader'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useCallback, useEffect, useState } from "react"
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { DiscoverItemFeed } from './DiscoverFeed/DiscoverFeed'
|
||||
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import toast from 'react-hot-toast'
|
||||
|
|
@ -10,13 +10,13 @@ import { Button } from '../../elements/Button'
|
|||
import { showErrorToast } from '../../../lib/toastHelpers'
|
||||
import {
|
||||
saveDiscoverArticleMutation,
|
||||
SaveDiscoverArticleOutput
|
||||
} from "../../../lib/networking/mutations/saveDiscoverArticle"
|
||||
import { saveUrlMutation } from "../../../lib/networking/mutations/saveUrlMutation"
|
||||
import { useFetchMore } from "../../../lib/hooks/useFetchMoreScroll"
|
||||
import { AddLinkModal } from "../homeFeed/AddLinkModal"
|
||||
import { useGetDiscoverFeedItems } from "../../../lib/networking/queries/useGetDiscoverFeedItems"
|
||||
import { useGetDiscoverFeeds } from "../../../lib/networking/queries/useGetDiscoverFeeds"
|
||||
SaveDiscoverArticleOutput,
|
||||
} from '../../../lib/networking/mutations/saveDiscoverArticle'
|
||||
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
|
||||
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
|
||||
import { AddLinkModal } from '../AddLinkModal'
|
||||
import { useGetDiscoverFeedItems } from '../../../lib/networking/queries/useGetDiscoverFeedItems'
|
||||
import { useGetDiscoverFeeds } from '../../../lib/networking/queries/useGetDiscoverFeeds'
|
||||
|
||||
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
|
||||
|
||||
|
|
@ -27,8 +27,8 @@ export function DiscoverContainer(): JSX.Element {
|
|||
const viewer = useGetViewerQuery()
|
||||
const [showFilterMenu, setShowFilterMenu] = useState(false)
|
||||
const [layoutType, setLayoutType] = useState<LayoutType>('GRID_LAYOUT')
|
||||
const [showAddLinkModal, setShowAddLinkModal] = useState(false);
|
||||
const {feeds, revalidate, isValidating} = useGetDiscoverFeeds()
|
||||
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
|
||||
const { feeds, revalidate, isValidating } = useGetDiscoverFeeds()
|
||||
const topics = [
|
||||
{
|
||||
title: 'Popular',
|
||||
|
|
@ -73,8 +73,16 @@ export function DiscoverContainer(): JSX.Element {
|
|||
},
|
||||
]
|
||||
|
||||
const [selectedFeed, setSelectedFeed] = useState("All Feeds");
|
||||
const { discoverItems, setTopic, activeTopic, isLoading, hasMore, setPage, page } = useGetDiscoverFeedItems(topics[1], selectedFeed)
|
||||
const [selectedFeed, setSelectedFeed] = useState('All Feeds')
|
||||
const {
|
||||
discoverItems,
|
||||
setTopic,
|
||||
activeTopic,
|
||||
isLoading,
|
||||
hasMore,
|
||||
setPage,
|
||||
page,
|
||||
} = useGetDiscoverFeedItems(topics[1], selectedFeed)
|
||||
const handleFetchMore = useCallback(() => {
|
||||
if (isLoading || !hasMore) {
|
||||
return
|
||||
|
|
@ -88,7 +96,11 @@ export function DiscoverContainer(): JSX.Element {
|
|||
timezone: string,
|
||||
locale: string
|
||||
): Promise<SaveDiscoverArticleOutput | undefined> => {
|
||||
const result = await saveDiscoverArticleMutation({discoverArticleId, timezone, locale})
|
||||
const result = await saveDiscoverArticleMutation({
|
||||
discoverArticleId,
|
||||
timezone,
|
||||
locale,
|
||||
})
|
||||
if (result?.saveDiscoverArticle) {
|
||||
toast(
|
||||
() => (
|
||||
|
|
@ -160,8 +172,8 @@ export function DiscoverContainer(): JSX.Element {
|
|||
}, [])
|
||||
|
||||
const setTopicAndReturnToTop = (topic: TopicTabData) => {
|
||||
window.scroll(0,0);
|
||||
setTopic(topic);
|
||||
window.scroll(0, 0)
|
||||
setTopic(topic)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -204,12 +216,12 @@ export function DiscoverContainer(): JSX.Element {
|
|||
items={discoverItems ?? []}
|
||||
viewer={viewer.viewerData?.me}
|
||||
/>
|
||||
{ showAddLinkModal &&
|
||||
{showAddLinkModal && (
|
||||
<AddLinkModal
|
||||
handleLinkSubmission={handleLinkSave}
|
||||
onOpenChange={() => setShowAddLinkModal(false)}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { HStack } from '../../../elements/LayoutPrimitives'
|
||||
import { TopicTab } from './TopicTab'
|
||||
import { CaretLeft, CaretRight } from 'phosphor-react'
|
||||
import { CaretLeft, CaretRight } from '@phosphor-icons/react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { TopicTabData } from '../DiscoverContainer'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { HStack } from '../../../elements/LayoutPrimitives'
|
||||
import { OmnivoreSmallLogo } from '../../../elements/images/OmnivoreNameLogo'
|
||||
import { theme } from '../../../tokens/stitches.config'
|
||||
import { FunnelSimple } from 'phosphor-react'
|
||||
import { FunnelSimple } from '@phosphor-icons/react'
|
||||
import { DiscoverHeaderProps } from './DiscoverHeader'
|
||||
import { SmallTopicBar } from './SmallTopicBar'
|
||||
import { PrimaryDropdown } from '../../PrimaryDropdown'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, HStack } from '../../../elements/LayoutPrimitives'
|
||||
import { TopicTab } from './TopicTab'
|
||||
import { CaretLeft, CaretRight } from 'phosphor-react'
|
||||
import { CaretLeft, CaretRight } from '@phosphor-icons/react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { TopicTabData } from '../DiscoverContainer'
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import {
|
|||
Browsers,
|
||||
MinusCircle,
|
||||
PlusCircle,
|
||||
} from 'phosphor-react'
|
||||
} from '@phosphor-icons/react'
|
||||
import { timeZone, locale } from '../../../../lib/dateFormatting'
|
||||
import React from 'react'
|
||||
import { SaveDiscoverArticleOutput } from "../../../../lib/networking/mutations/saveDiscoverArticle"
|
||||
import { DiscoverFeedItem } from "../../../../lib/networking/queries/useGetDiscoverFeedItems"
|
||||
import { BrowserIcon } from "../../../elements/icons/BrowserIcon"
|
||||
import { SaveDiscoverArticleOutput } from '../../../../lib/networking/mutations/saveDiscoverArticle'
|
||||
import { DiscoverFeedItem } from '../../../../lib/networking/queries/useGetDiscoverFeedItems'
|
||||
import { BrowserIcon } from '../../../elements/icons/BrowserIcon'
|
||||
|
||||
type DiscoverHoverActionsProps = {
|
||||
viewer?: UserBasicData
|
||||
|
|
@ -29,7 +29,7 @@ type DiscoverHoverActionsProps = {
|
|||
setSavedUrl: (url: string) => void
|
||||
savedUrl?: string
|
||||
|
||||
deleteDiscoverItem: (item: DiscoverFeedItem) => Promise<void>,
|
||||
deleteDiscoverItem: (item: DiscoverFeedItem) => Promise<void>
|
||||
}
|
||||
|
||||
export const DiscoverHoverActions = (props: DiscoverHoverActionsProps) => {
|
||||
|
|
@ -60,14 +60,14 @@ export const DiscoverHoverActions = (props: DiscoverHoverActionsProps) => {
|
|||
>
|
||||
<Button
|
||||
title={
|
||||
(props.savedId && 'Remove From Library (A)') ||
|
||||
'Add to Library (A)'
|
||||
(props.savedId && 'Remove From Library (A)') || 'Add to Library (A)'
|
||||
}
|
||||
style="hoverActionIcon"
|
||||
onClick={(event) => {
|
||||
console.log(props);
|
||||
console.log(props)
|
||||
if (!props.savedUrl) {
|
||||
props.handleLinkSubmission(props.item.id, timeZone, locale)
|
||||
props
|
||||
.handleLinkSubmission(props.item.id, timeZone, locale)
|
||||
.then((item) => {
|
||||
if (item) {
|
||||
props.setSavedId(item.saveDiscoverArticle.saveId)
|
||||
|
|
@ -120,7 +120,10 @@ export const DiscoverHoverActions = (props: DiscoverHoverActionsProps) => {
|
|||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<BrowserIcon size={21} color={theme.colors.thNotebookSubtle.toString()} />
|
||||
<BrowserIcon
|
||||
size={21}
|
||||
color={theme.colors.thNotebookSubtle.toString()}
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ import {
|
|||
siteName,
|
||||
TitleStyle,
|
||||
} from '../../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { DiscoverItemCardProps, DiscoverItemSubCardProps } from "./DiscoverItemCard"
|
||||
import {
|
||||
DiscoverItemCardProps,
|
||||
DiscoverItemSubCardProps,
|
||||
} from './DiscoverItemCard'
|
||||
import { DiscoverItemMetadata } from './DiscoverItemMetadata'
|
||||
import { DiscoverHoverActions } from './DiscoverHoverActions'
|
||||
import { CheckCircle, Circle } from 'phosphor-react'
|
||||
import { CheckCircle, Circle } from '@phosphor-icons/react'
|
||||
|
||||
export function DiscoverGridCard(props: DiscoverItemSubCardProps): JSX.Element {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
|
@ -100,7 +103,12 @@ export function DiscoverGridCard(props: DiscoverItemSubCardProps): JSX.Element {
|
|||
/>
|
||||
</Box>
|
||||
)}
|
||||
<DiscoverGridCardContent {...props} savedId={props.savedId} savedUrl={props.savedUrl} isHovered={isHovered} />
|
||||
<DiscoverGridCardContent
|
||||
{...props}
|
||||
savedId={props.savedId}
|
||||
savedUrl={props.savedUrl}
|
||||
isHovered={isHovered}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -122,7 +130,15 @@ const DiscoverGridCardContent = (
|
|||
}
|
||||
|
||||
return (
|
||||
<VStack css={{ p: '0px', m: '0px', width: '100%', cursor: props.savedId ? 'pointer' : 'default' }} onClick={goToUrl} >
|
||||
<VStack
|
||||
css={{
|
||||
p: '0px',
|
||||
m: '0px',
|
||||
width: '100%',
|
||||
cursor: props.savedId ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={goToUrl}
|
||||
>
|
||||
<Box css={{ position: 'relative', width: '100%', height: '150px' }}>
|
||||
<>
|
||||
<HStack
|
||||
|
|
@ -161,7 +177,7 @@ const DiscoverGridCardContent = (
|
|||
height="150px"
|
||||
css={{
|
||||
bg: '$thBackground',
|
||||
cursor: props.savedId ? 'pointer' : 'default'
|
||||
cursor: props.savedId ? 'pointer' : 'default',
|
||||
}}
|
||||
onError={(e) => {
|
||||
setDisplayFallback(true)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,11 @@ import {
|
|||
siteName,
|
||||
TitleStyle,
|
||||
} from '../../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { CheckCircle, Circle } from 'phosphor-react'
|
||||
import { DiscoverItemCardProps, DiscoverItemSubCardProps } from "./DiscoverItemCard"
|
||||
import { CheckCircle, Circle } from '@phosphor-icons/react'
|
||||
import {
|
||||
DiscoverItemCardProps,
|
||||
DiscoverItemSubCardProps,
|
||||
} from './DiscoverItemCard'
|
||||
import { DiscoverItemMetadata } from './DiscoverItemMetadata'
|
||||
import { DiscoverHoverActions } from './DiscoverHoverActions'
|
||||
|
||||
|
|
@ -103,13 +106,17 @@ export function DiscoverItemListCard(
|
|||
/>
|
||||
</Box>
|
||||
)}
|
||||
<DiscoverListCardContent {...props} savedId={props.savedId} isHovered={isOpen} />
|
||||
<DiscoverListCardContent
|
||||
{...props}
|
||||
savedId={props.savedId}
|
||||
isHovered={isOpen}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscoverListCardContent(
|
||||
props: DiscoverItemCardProps & { savedId?: string; savedUrl? : string }
|
||||
props: DiscoverItemCardProps & { savedId?: string; savedUrl?: string }
|
||||
): JSX.Element {
|
||||
const originText = siteName(props.item.url, props.item.url)
|
||||
const [displayFallback, setDisplayFallback] = useState(
|
||||
|
|
@ -123,7 +130,14 @@ export function DiscoverListCardContent(
|
|||
}
|
||||
|
||||
return (
|
||||
<HStack css={{ gap: '15px', width: '100%', cursor: props.savedId ? 'pointer' : 'default' }} onClick={goToUrl} >
|
||||
<HStack
|
||||
css={{
|
||||
gap: '15px',
|
||||
width: '100%',
|
||||
cursor: props.savedId ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={goToUrl}
|
||||
>
|
||||
<Box css={{ position: 'relative', width: '55px' }}>
|
||||
<HStack
|
||||
css={{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Book } from 'phosphor-react'
|
||||
import { Book } from '@phosphor-icons/react'
|
||||
import { VStack } from '../../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Link from 'next/link'
|
||||
import { DotsThreeVertical } from 'phosphor-react'
|
||||
import { DotsThreeVertical } from '@phosphor-icons/react'
|
||||
import { useCallback } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
|
|
@ -108,7 +108,8 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
<DropdownSeparator />
|
||||
<Link
|
||||
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
>
|
||||
<StyledLinkItem
|
||||
onClick={(event) => {
|
||||
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
||||
|
|
@ -129,7 +130,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
</Link>
|
||||
</Dropdown>
|
||||
</VStack>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const sortHighlights = (highlights: Highlight[]) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { HighlighterCircle } from 'phosphor-react'
|
||||
import { HighlighterCircle } from '@phosphor-icons/react'
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
|
|
@ -36,8 +36,9 @@ type HighlightItemsLayoutProps = {
|
|||
export function HighlightItemsLayout(
|
||||
props: HighlightItemsLayoutProps
|
||||
): JSX.Element {
|
||||
const [currentItem, setCurrentItem] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
const [currentItem, setCurrentItem] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const listReducer = (
|
||||
state: LibraryItem[],
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
|||
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
|
||||
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
|
||||
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
|
||||
import { AddLinkModal } from './AddLinkModal'
|
||||
import { AddLinkModal } from '../AddLinkModal'
|
||||
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 { LegacyLibraryHeader, MultiSelectMode } from './LibraryHeader'
|
||||
import { UploadModal } from '../UploadModal'
|
||||
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
|
|
@ -116,7 +116,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
performActionOnItem,
|
||||
mutate,
|
||||
error: fetchItemsError,
|
||||
} = useGetLibraryItemsQuery(queryInputs)
|
||||
} = useGetLibraryItemsQuery('inbox', queryInputs)
|
||||
|
||||
useEffect(() => {
|
||||
const handleRevalidate = () => {
|
||||
|
|
@ -975,7 +975,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
}}
|
||||
>
|
||||
{props.mode != 'highlights' && (
|
||||
<LibraryHeader
|
||||
<LegacyLibraryHeader
|
||||
layout={layout}
|
||||
viewer={viewerData?.me}
|
||||
updateLayout={updateLayout}
|
||||
|
|
@ -1331,6 +1331,7 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
|
|||
>
|
||||
{props.viewer && (
|
||||
<LinkedItemCard
|
||||
legacyLayout={true}
|
||||
layout={props.layout}
|
||||
item={linkedItem.node}
|
||||
isLoading={linkedItem.isLoading}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { FormInput } from '../../elements/FormElements'
|
|||
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { Button, IconButton } from '../../elements/Button'
|
||||
import { FunnelSimple, X } from 'phosphor-react'
|
||||
import { FunnelSimple, X } from '@phosphor-icons/react'
|
||||
import { LayoutType, LibraryMode } from './HomeFeedContainer'
|
||||
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
|
||||
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from './HeaderSpacer'
|
||||
|
|
@ -63,7 +63,7 @@ export const headerControlWidths = (
|
|||
}
|
||||
}
|
||||
|
||||
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
||||
export function LegacyLibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
||||
const [small, setSmall] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -88,7 +88,6 @@ export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
|||
right: '0',
|
||||
zIndex: 5,
|
||||
px: '70px',
|
||||
bg: '$thLibraryBackground',
|
||||
position: 'fixed',
|
||||
left: LIBRARY_LEFT_MENU_WIDTH,
|
||||
height: small ? '60px' : DEFAULT_HEADER_HEIGHT,
|
||||
|
|
|
|||
|
|
@ -8,13 +8,30 @@ import { LabelIcon } from '../../elements/icons/LabelIcon'
|
|||
import { TrashIcon } from '../../elements/icons/TrashIcon'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal'
|
||||
import { X } from 'phosphor-react'
|
||||
import { LibraryHeaderProps } from './LibraryHeader'
|
||||
import { X } from '@phosphor-icons/react'
|
||||
import { MultiSelectMode } from './LibraryHeader'
|
||||
import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { MarkAsReadIcon } from '../../elements/icons/MarkAsReadIcon'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => {
|
||||
export type MultiSelectProps = {
|
||||
viewer: UserBasicData | undefined
|
||||
|
||||
searchTerm: string | undefined
|
||||
applySearchQuery: (searchQuery: string) => void
|
||||
|
||||
showFilterMenu: boolean
|
||||
setShowFilterMenu: (show: boolean) => void
|
||||
|
||||
numItemsSelected: number
|
||||
multiSelectMode: MultiSelectMode
|
||||
setMultiSelectMode: (mode: MultiSelectMode) => void
|
||||
|
||||
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
|
||||
}
|
||||
|
||||
export const MultiSelectControls = (props: MultiSelectProps): JSX.Element => {
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [showLabelsModal, setShowLabelsModal] = useState(false)
|
||||
// Don't change on immediate hover, the button has to be blurred at least once
|
||||
|
|
@ -146,7 +163,7 @@ export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => {
|
|||
)
|
||||
}
|
||||
|
||||
export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => {
|
||||
export const CheckBoxButton = (props: MultiSelectProps): JSX.Element => {
|
||||
return (
|
||||
<Button
|
||||
title="Select multiple"
|
||||
|
|
@ -171,7 +188,7 @@ export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => {
|
|||
)
|
||||
}
|
||||
|
||||
export const ArchiveButton = (props: LibraryHeaderProps): JSX.Element => {
|
||||
export const ArchiveButton = (props: MultiSelectProps): JSX.Element => {
|
||||
const [color, setColor] = useState<string>(
|
||||
theme.colors.thTextContrast2.toString()
|
||||
)
|
||||
|
|
@ -206,7 +223,7 @@ export const ArchiveButton = (props: LibraryHeaderProps): JSX.Element => {
|
|||
)
|
||||
}
|
||||
|
||||
export const MarkAsReadButton = (props: LibraryHeaderProps): JSX.Element => {
|
||||
export const MarkAsReadButton = (props: MultiSelectProps): JSX.Element => {
|
||||
const [color, setColor] = useState<string>(
|
||||
theme.colors.thTextContrast2.toString()
|
||||
)
|
||||
|
|
@ -321,7 +338,7 @@ export const RemoveItemsButton = (
|
|||
)
|
||||
}
|
||||
|
||||
export const CancelButton = (props: LibraryHeaderProps): JSX.Element => {
|
||||
export const CancelButton = (props: MultiSelectProps): JSX.Element => {
|
||||
const [color, setColor] = useState<string>(
|
||||
theme.colors.thTextContrast2.toString()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { BrowserIcon } from '../../elements/icons/BrowserIcon'
|
|||
import { styled } from '@stitches/react'
|
||||
import { siteName } from '../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { DotsThree } from '@phosphor-icons/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
type TLDRLayoutProps = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Image from 'next/image'
|
|||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
|
||||
import { Link, Plus } from 'phosphor-react'
|
||||
import { Link, Plus } from '@phosphor-icons/react'
|
||||
import { useGetWebhooksQuery } from '../../../lib/networking/queries/useGetWebhooksQuery'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
|
|
|||
1259
packages/web/components/templates/library/LibraryContainer.tsx
Normal file
1259
packages/web/components/templates/library/LibraryContainer.tsx
Normal file
File diff suppressed because it is too large
Load diff
411
packages/web/components/templates/library/LibraryHeader.tsx
Normal file
411
packages/web/components/templates/library/LibraryHeader.tsx
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
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'
|
||||
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { Button, IconButton } from '../../elements/Button'
|
||||
import { FunnelSimple, X } from '@phosphor-icons/react'
|
||||
import { LayoutType, LibraryMode } from '../homeFeed/HomeFeedContainer'
|
||||
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
|
||||
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from '../homeFeed/HeaderSpacer'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
|
||||
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
|
||||
import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon'
|
||||
import { HeaderToggleTLDRIcon } from '../../elements/icons/HeaderToggleTLDRIcon'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { userHasFeature } from '../../../lib/featureFlag'
|
||||
import {
|
||||
MultiSelectControls,
|
||||
CheckBoxButton,
|
||||
} from '../homeFeed/MultiSelectControls'
|
||||
|
||||
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
|
||||
|
||||
export type LibraryHeaderProps = {
|
||||
viewer: UserBasicData | undefined
|
||||
|
||||
layout: LayoutType
|
||||
updateLayout: (layout: LayoutType) => void
|
||||
|
||||
searchTerm: string | undefined
|
||||
applySearchQuery: (searchQuery: string) => void
|
||||
|
||||
showFilterMenu: boolean
|
||||
setShowFilterMenu: (show: boolean) => void
|
||||
|
||||
numItemsSelected: number
|
||||
multiSelectMode: MultiSelectMode
|
||||
setMultiSelectMode: (mode: MultiSelectMode) => void
|
||||
|
||||
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
|
||||
}
|
||||
|
||||
export const headerControlWidths = (
|
||||
layout: LayoutType,
|
||||
multiSelectMode: MultiSelectMode
|
||||
) => {
|
||||
return {
|
||||
width: '95%',
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
'@media (min-width: 930px)': {
|
||||
width: '620px',
|
||||
},
|
||||
'@media (min-width: 1280px)': {
|
||||
width: '940px',
|
||||
},
|
||||
'@media (min-width: 1600px)': {
|
||||
width: '1232px',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
||||
const [small, setSmall] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setSmall(window.scrollY > 40)
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
px: '70px',
|
||||
left: LIBRARY_LEFT_MENU_WIDTH,
|
||||
transition: 'height 0.5s',
|
||||
'@lgDown': { px: '20px' },
|
||||
'@mdDown': {
|
||||
px: '10px',
|
||||
left: '0px',
|
||||
right: '0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LargeHeaderLayout {...props} />
|
||||
</VStack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
|
||||
return (
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
css={{
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
{props.multiSelectMode !== 'off' ? (
|
||||
<>
|
||||
<MultiSelectControls {...props} />
|
||||
</>
|
||||
) : (
|
||||
<HeaderControls {...props} />
|
||||
)}
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
const HeaderControls = (props: LibraryHeaderProps): JSX.Element => {
|
||||
const [searchBoxFocused, setSearchBoxFocused] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!searchBoxFocused && (
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'none',
|
||||
'@mdDown': { display: 'flex' },
|
||||
}}
|
||||
>
|
||||
<MenuHeaderButton {...props} />
|
||||
</SpanBox>
|
||||
)}
|
||||
|
||||
<SearchBox
|
||||
{...props}
|
||||
searchBoxFocused={searchBoxFocused}
|
||||
setSearchBoxFocused={setSearchBoxFocused}
|
||||
/>
|
||||
|
||||
<SpanBox css={{ display: 'flex', ml: 'auto', gap: '10px' }}>
|
||||
{/* {userHasFeature(props.viewer, 'ai-summaries') && (
|
||||
<Button
|
||||
title="TLDR Summaries"
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
marginLeft: 'auto',
|
||||
'&:hover': { opacity: '1.0' },
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (props.mode == 'reads') {
|
||||
props.setMode('tldr')
|
||||
} else {
|
||||
props.setMode('reads')
|
||||
}
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<HeaderToggleTLDRIcon />
|
||||
</Button>
|
||||
)} */}
|
||||
|
||||
<Button
|
||||
title={
|
||||
props.layout == 'GRID_LAYOUT'
|
||||
? 'Switch to list layout'
|
||||
: 'Switch to grid layout'
|
||||
}
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
marginLeft: 'auto',
|
||||
'&:hover': { opacity: '1.0' },
|
||||
}}
|
||||
onClick={(e) => {
|
||||
props.updateLayout(
|
||||
props.layout == 'GRID_LAYOUT' ? 'LIST_LAYOUT' : 'GRID_LAYOUT'
|
||||
)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.layout == 'LIST_LAYOUT' ? (
|
||||
<HeaderToggleGridIcon />
|
||||
) : (
|
||||
<HeaderToggleListIcon />
|
||||
)}
|
||||
</Button>
|
||||
</SpanBox>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type MenuHeaderButtonProps = {
|
||||
showFilterMenu: boolean
|
||||
setShowFilterMenu: (show: boolean) => void
|
||||
}
|
||||
|
||||
export function MenuHeaderButton(props: MenuHeaderButtonProps): JSX.Element {
|
||||
return (
|
||||
<HStack
|
||||
css={{
|
||||
width: '67px',
|
||||
height: '40px',
|
||||
bg: props.showFilterMenu ? '$thTextContrast2' : '$thBackground2',
|
||||
borderRadius: '5px',
|
||||
px: '5px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
alignment="center"
|
||||
distribution="around"
|
||||
onClick={() => {
|
||||
props.setShowFilterMenu(!props.showFilterMenu)
|
||||
}}
|
||||
>
|
||||
<OmnivoreSmallLogo
|
||||
size={20}
|
||||
strokeColor={
|
||||
props.showFilterMenu
|
||||
? theme.colors.thBackground.toString()
|
||||
: theme.colors.thTextContrast2.toString()
|
||||
}
|
||||
/>
|
||||
<FunnelSimple
|
||||
size={20}
|
||||
color={
|
||||
props.showFilterMenu
|
||||
? theme.colors.thBackground.toString()
|
||||
: theme.colors.thTextContrast2.toString()
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
type SearchBoxProps = LibraryHeaderProps & {
|
||||
searchBoxFocused: boolean
|
||||
setSearchBoxFocused: (show: boolean) => void
|
||||
}
|
||||
|
||||
export function SearchBox(props: SearchBoxProps): JSX.Element {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
const [searchTerm, setSearchTerm] = useState(props.searchTerm ?? '')
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(props.searchTerm ?? '')
|
||||
}, [props.searchTerm])
|
||||
|
||||
useKeyboardShortcuts(
|
||||
searchBarCommands((action) => {
|
||||
if (action === 'focusSearchBar' && inputRef.current) {
|
||||
inputRef.current.select()
|
||||
}
|
||||
if (action == 'clearSearch' && inputRef.current) {
|
||||
setSearchTerm('')
|
||||
props.applySearchQuery('')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
height: '38px',
|
||||
width: '100%',
|
||||
maxWidth: '521px',
|
||||
bg: '$thLibrarySearchbox',
|
||||
borderRadius: '6px',
|
||||
boxShadow: props.searchBoxFocused
|
||||
? 'none'
|
||||
: '0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
|
||||
}}
|
||||
>
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
css={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{
|
||||
width: '53px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
bg: props.multiSelectMode !== 'off' ? '$ctaBlue' : 'transparent',
|
||||
borderTopLeftRadius: '6px',
|
||||
borderBottomLeftRadius: '6px',
|
||||
'--checkbox-color': 'var(--colors-thLibraryMultiselectCheckbox)',
|
||||
'&:hover': {
|
||||
bg: '$thLibraryMultiselectHover',
|
||||
'--checkbox-color':
|
||||
'var(--colors-thLibraryMultiselectCheckboxHover)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CheckBoxButton {...props} />
|
||||
</HStack>
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
css={{
|
||||
border: props.searchBoxFocused
|
||||
? '2px solid $searchActiveOutline'
|
||||
: '2px solid transparent',
|
||||
borderTopRightRadius: '6px',
|
||||
borderBottomRightRadius: '6px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault()
|
||||
props.applySearchQuery(searchTerm || '')
|
||||
inputRef.current?.blur()
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<FormInput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
autoFocus={false}
|
||||
placeholder="Search keywords or labels"
|
||||
onFocus={(event) => {
|
||||
event.target.select()
|
||||
props.setSearchBoxFocused(true)
|
||||
}}
|
||||
onBlur={() => {
|
||||
props.setSearchBoxFocused(false)
|
||||
}}
|
||||
onChange={(event) => {
|
||||
setSearchTerm(event.target.value)
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
const key = event.key.toLowerCase()
|
||||
if (key == 'escape') {
|
||||
event.currentTarget.blur()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
<HStack
|
||||
alignment="center"
|
||||
css={{
|
||||
py: '15px',
|
||||
mr: '10px',
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<CancelSearchButton
|
||||
onClick={() => {
|
||||
setSearchTerm('in:inbox')
|
||||
props.applySearchQuery('')
|
||||
inputRef.current?.blur()
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
type CancelSearchButtonProps = {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const CancelSearchButton = (props: CancelSearchButtonProps): JSX.Element => {
|
||||
const [color, setColor] = useState<string>(
|
||||
theme.colors.thTextContrast2.toString()
|
||||
)
|
||||
return (
|
||||
<Button
|
||||
title="Cancel"
|
||||
style="plainIcon"
|
||||
css={{
|
||||
p: '5px',
|
||||
display: 'flex',
|
||||
'&:hover': {
|
||||
bg: '$ctaBlue',
|
||||
borderRadius: '100px',
|
||||
opacity: 1.0,
|
||||
},
|
||||
}}
|
||||
onMouseEnter={(event) => {
|
||||
setColor('white')
|
||||
event.preventDefault()
|
||||
}}
|
||||
onMouseLeave={(event) => {
|
||||
setColor(theme.colors.thTextContrast2.toString())
|
||||
event.preventDefault()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
props.onClick()
|
||||
}}
|
||||
>
|
||||
<X width={19} height={19} color={color} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Allotment } from 'allotment'
|
||||
import 'allotment/dist/style.css'
|
||||
import { LibraryContainer } from './LibraryContainer'
|
||||
|
||||
export function LibraryItemsContainer(): JSX.Element {
|
||||
return (
|
||||
<Allotment>
|
||||
<Allotment.Pane minSize={200}>
|
||||
<LibraryContainer folder="inbox" />
|
||||
</Allotment.Pane>
|
||||
{/* <Allotment.Pane snap maxSize={400}>
|
||||
<HighlightsList />
|
||||
</Allotment.Pane> */}
|
||||
</Allotment>
|
||||
)
|
||||
}
|
||||
62
packages/web/components/templates/library/LibrarySideBar.tsx
Normal file
62
packages/web/components/templates/library/LibrarySideBar.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import TopBarProgress from 'react-topbar-progress-indicator'
|
||||
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
|
||||
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
|
||||
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import {
|
||||
PageType,
|
||||
State,
|
||||
} from '../../../lib/networking/fragments/articleFragment'
|
||||
import {
|
||||
SearchItem,
|
||||
TypeaheadSearchItemsData,
|
||||
typeaheadSearchQuery,
|
||||
} from '../../../lib/networking/queries/typeaheadSearch'
|
||||
import type {
|
||||
LibraryItem,
|
||||
LibraryItemsQueryInput,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import {
|
||||
useGetViewerQuery,
|
||||
UserBasicData,
|
||||
} from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
|
||||
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { AddLinkModal } from '../AddLinkModal'
|
||||
import { EditLibraryItemModal } from '../homeFeed/EditItemModals'
|
||||
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
|
||||
import { LegacyLibraryHeader, MultiSelectMode } from '../homeFeed/LibraryHeader'
|
||||
import { UploadModal } from '../UploadModal'
|
||||
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
import {
|
||||
showErrorToast,
|
||||
showSuccessToast,
|
||||
showSuccessToastWithAction,
|
||||
} from '../../../lib/toastHelpers'
|
||||
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
|
||||
import { NotebookPresenter } from '../article/NotebookPresenter'
|
||||
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
|
||||
import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
|
||||
import { PinnedButtons } from '../homeFeed/PinnedButtons'
|
||||
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
|
||||
import { FetchItemsError } from '../homeFeed/FetchItemsError'
|
||||
import { LibraryHeader } from './LibraryHeader'
|
||||
|
||||
type LibrarySideBarProps = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export function LibrarySideBar(props: LibrarySideBarProps): JSX.Element {
|
||||
return <VStack css={{ width: '100%', height: '100%' }}>{props.text}</VStack>
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ export const NavMenuFooter = (props: NavMenuFooterProps): JSX.Element => {
|
|||
position: 'fixed',
|
||||
bottom: '0%',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '$thBackground2',
|
||||
backgroundColor: '$thNavMenuFooter',
|
||||
width: LIBRARY_LEFT_MENU_WIDTH,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
|
|
@ -29,10 +29,7 @@ export const NavMenuFooter = (props: NavMenuFooterProps): JSX.Element => {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<PrimaryDropdown
|
||||
showThemeSection={!props.showFullThemeSection ?? true}
|
||||
showFullThemeSection={props.showFullThemeSection ?? false}
|
||||
/>
|
||||
<PrimaryDropdown />
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
|
|
|
|||
|
|
@ -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, NewspaperClipping, X } from 'phosphor-react'
|
||||
import { Circle, X } from '@phosphor-icons/react'
|
||||
import {
|
||||
Subscription,
|
||||
SubscriptionType,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { StyledText } from '../../elements/StyledText'
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { Circle, DotsThree, MagnifyingGlass, X } from 'phosphor-react'
|
||||
import { Circle, DotsThree, MagnifyingGlass, X } from '@phosphor-icons/react'
|
||||
import {
|
||||
Subscription,
|
||||
SubscriptionType,
|
||||
|
|
@ -25,7 +25,7 @@ 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 { Shortcut } from './NavigationMenu'
|
||||
import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip'
|
||||
import { NewsletterIcon } from '../../elements/icons/NewsletterIcon'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
|
|
@ -46,55 +46,6 @@ type LibraryFilterMenuProps = {
|
|||
}
|
||||
|
||||
export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
|
||||
const [labels, setLabels] = usePersistedState<Label[]>({
|
||||
key: 'menu-labels',
|
||||
isSessionStorage: false,
|
||||
initialValue: [],
|
||||
})
|
||||
const [savedSearches, setSavedSearches] = usePersistedState<SavedSearch[]>({
|
||||
key: 'menu-searches',
|
||||
isSessionStorage: false,
|
||||
initialValue: [],
|
||||
})
|
||||
const [subscriptions, setSubscriptions] = usePersistedState<Subscription[]>({
|
||||
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 (
|
||||
<>
|
||||
<Box
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,7 @@ import {
|
|||
DropdownSeparator,
|
||||
} from '../../elements/DropdownElements'
|
||||
import { useRouter } from 'next/router'
|
||||
import { List } from 'phosphor-react'
|
||||
import { List } from '@phosphor-icons/react'
|
||||
|
||||
export const SettingsDropdown = (): JSX.Element => {
|
||||
const router = useRouter()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { LogoBox } from '../../elements/LogoBox'
|
|||
import Link from 'next/link'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { ArrowSquareUpRight } from 'phosphor-react'
|
||||
import { ArrowSquareUpRight } from '@phosphor-icons/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NavMenuFooter } from './Footer'
|
||||
|
||||
|
|
|
|||
|
|
@ -144,12 +144,6 @@ function ControlButtonBox(props: ReaderHeaderProps): JSX.Element {
|
|||
color={theme.colors.thHighContrast.toString()}
|
||||
/>
|
||||
</Button>
|
||||
<PrimaryDropdown showThemeSection={false} showFullThemeSection={false}>
|
||||
<CircleUtilityMenuIcon
|
||||
size={25}
|
||||
color={theme.colors.thHighContrast.toString()}
|
||||
/>
|
||||
</PrimaryDropdown>
|
||||
</HStack>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Pencil, Trash } from 'phosphor-react'
|
||||
import { Pencil, Trash } from '@phosphor-icons/react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
|||
searchActiveOutline: 'rgb(255, 210, 52)',
|
||||
|
||||
// Reader Colors
|
||||
readerBg: 'white',
|
||||
readerBg: '#FAFAFA',
|
||||
readerFont: '#3D3D3D',
|
||||
readerFontHighContrast: 'black',
|
||||
readerTableHeader: '#FFFFFF',
|
||||
|
|
@ -180,7 +180,8 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
|||
thBackground5: '#F5F5F5',
|
||||
thBackgroundActive: '#FFEA9F',
|
||||
thBackgroundContrast: '#FFFFFF',
|
||||
thLeftMenuBackground: '#FCFCFC',
|
||||
thLeftMenuBackground: '#F2F2F2',
|
||||
thNavMenuFooter: '#DFDFDF',
|
||||
thLibraryBackground: '#FFFFFF',
|
||||
thLibrarySearchbox: '#FCFCFC',
|
||||
thLibraryMenuPrimary: '#3D3D3D',
|
||||
|
|
@ -229,6 +230,14 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
|||
thHighContrast: '#3D3D3D',
|
||||
thHighlightBar: '#D9D9D9',
|
||||
|
||||
homeCardHover: '#FFFFFF',
|
||||
homeTextTitle: '#2A2A2A',
|
||||
homeTextSource: '#3D3D3D',
|
||||
homeTextBody: '#3D3D3D',
|
||||
homeTextSubtle: '#898989',
|
||||
homeActionIcons: '#898989',
|
||||
homeDivider: '#D9D9D9',
|
||||
|
||||
thLibraryAISummaryBorder: '#6A6968',
|
||||
thLibraryAISummaryBackground: '#343434',
|
||||
|
||||
|
|
@ -324,12 +333,13 @@ const darkThemeSpec = {
|
|||
thBackgroundActive: '#3D3D3D',
|
||||
thBackgroundContrast: '#000000',
|
||||
thLeftMenuBackground: '#343434',
|
||||
thNavMenuFooter: '#515151',
|
||||
thLibraryBackground: '#2A2A2A',
|
||||
thLibrarySearchbox: '#3D3D3D',
|
||||
thLibraryMenuPrimary: '#EBEBEB',
|
||||
thLibraryMenuSecondary: '#EBEBEB',
|
||||
thLibraryMenuUnselected: 'white',
|
||||
thLibrarySelectionColor: '#6A6968',
|
||||
thLibrarySelectionColor: '#515151',
|
||||
thLibraryNavigationMenuFooter: '#3D3D3D',
|
||||
thLibraryMenuFooterHover: '#6A6968',
|
||||
thLibraryMultiselectHover: '#6A6968',
|
||||
|
|
@ -374,6 +384,14 @@ const darkThemeSpec = {
|
|||
|
||||
thHighlightBar: '#6A6968',
|
||||
|
||||
homeCardHover: '#323232',
|
||||
homeTextTitle: '#FFFFFF',
|
||||
homeTextSource: '#D9D9D9',
|
||||
homeTextBody: '#D9D9D9',
|
||||
homeTextSubtle: '#898989',
|
||||
homeActionIcons: '#898989',
|
||||
homeDivider: '#3D3D3D',
|
||||
|
||||
thLibraryAISummaryBorder: '#6A6968',
|
||||
thLibraryAISummaryBackground: '#343434',
|
||||
|
||||
|
|
@ -392,19 +410,6 @@ const blackThemeSpec = {
|
|||
},
|
||||
}
|
||||
|
||||
const sepiaThemeSpec = {
|
||||
colorScheme: {
|
||||
colorScheme: 'light',
|
||||
},
|
||||
colors: {
|
||||
readerBg: '#FBF0D9',
|
||||
readerFont: '#5F4B32',
|
||||
readerMargin: '#F3F3F3',
|
||||
readerFontHighContrast: '#0A0806',
|
||||
readerTableHeader: '#FFFFFF',
|
||||
},
|
||||
}
|
||||
|
||||
const apolloThemeSpec = {
|
||||
colors: {
|
||||
readerBg: '#6A6968',
|
||||
|
|
@ -412,6 +417,47 @@ const apolloThemeSpec = {
|
|||
readerMargin: '#474747',
|
||||
readerFontHighContrast: 'white',
|
||||
readerTableHeader: '#FFFFFF',
|
||||
|
||||
thLeftMenuBackground: '#3D3D3D',
|
||||
thNavMenuFooter: '#515151',
|
||||
|
||||
thLibrarySelectionColor: '#515151',
|
||||
thBackground4: '#51515166', // used on hover of nav menu items
|
||||
thBorderColor: '#6A6968',
|
||||
|
||||
homeCardHover: '#525252',
|
||||
homeDivider: '#6A6968',
|
||||
|
||||
thBackground: '#474747',
|
||||
thBackground2: '#515151',
|
||||
thLibraryMultiselectHover: '#EEE8D5',
|
||||
},
|
||||
}
|
||||
|
||||
const sepiaThemeSpec = {
|
||||
colorScheme: {
|
||||
colorScheme: 'light',
|
||||
},
|
||||
colors: {
|
||||
readerBg: '#FDF6E3',
|
||||
readerFont: '#5F4B32',
|
||||
readerMargin: '#F3F3F3',
|
||||
readerFontHighContrast: '#0A0806',
|
||||
readerTableHeader: '#FFFFFF',
|
||||
|
||||
thLeftMenuBackground: '#EEE8D5',
|
||||
thNavMenuFooter: '#DDD6C1',
|
||||
|
||||
thLibrarySelectionColor: '#DDD6C1',
|
||||
thBackground4: '#DDD6C166', // used on hover of menu items
|
||||
thBorderColor: '#DDD6C1',
|
||||
|
||||
thBackground: '#FDF6E3',
|
||||
|
||||
homeCardHover: '#EEE8D5',
|
||||
homeDivider: '#DDD6C1',
|
||||
|
||||
thLibraryMultiselectHover: '#EEE8D5',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export type RequestContext = {
|
|||
}
|
||||
}
|
||||
|
||||
function requestHeaders(): Record<string, string> {
|
||||
export function requestHeaders(): Record<string, string> {
|
||||
const authToken = window?.localStorage.getItem('authToken') || undefined
|
||||
const pendingAuthToken =
|
||||
window?.localStorage.getItem('pendingUserAuth') || undefined
|
||||
|
|
@ -54,6 +54,7 @@ export function gqlFetcher(
|
|||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
})
|
||||
|
||||
return graphQLClient.request(query, variables, requestHeaders())
|
||||
}
|
||||
|
||||
|
|
@ -68,10 +69,14 @@ export function apiFetcher(path: string): Promise<unknown> {
|
|||
})
|
||||
}
|
||||
|
||||
export function apiPoster(path: string, body: any): Promise<Response> {
|
||||
export function apiPoster(
|
||||
path: string,
|
||||
body: any,
|
||||
method = 'POST'
|
||||
): Promise<Response> {
|
||||
const url = new URL(path, fetchEndpoint)
|
||||
return fetch(url.toString(), {
|
||||
method: 'POST',
|
||||
method: method,
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
|
|
@ -83,17 +88,19 @@ export function apiPoster(path: string, body: any): Promise<Response> {
|
|||
}
|
||||
|
||||
export function makePublicGqlFetcher(
|
||||
gql: string,
|
||||
variables?: unknown
|
||||
): (query: string) => Promise<unknown> {
|
||||
return (query: string) => gqlFetcher(query, variables, false)
|
||||
return (query: string) => gqlFetcher(gql, variables, false)
|
||||
}
|
||||
|
||||
// Partially apply gql variables to the request
|
||||
// This avoids using an object for the swr cache key
|
||||
export function makeGqlFetcher(
|
||||
gql: string,
|
||||
variables?: unknown
|
||||
): (query: string) => Promise<unknown> {
|
||||
return (query: string) => gqlFetcher(query, variables, true)
|
||||
return (query: string) => gqlFetcher(gql, variables, true)
|
||||
}
|
||||
|
||||
export function ssrFetcher(
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ export function useGetArticleOriginalHtmlQuery({
|
|||
|
||||
const { data } = useSWRImmutable(
|
||||
slug ? [query, username, slug] : null,
|
||||
makeGqlFetcher(variables)
|
||||
makeGqlFetcher(query, variables),
|
||||
{}
|
||||
)
|
||||
|
||||
const resultData: ArticleData | undefined = data as ArticleData
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
State,
|
||||
} from '../fragments/articleFragment'
|
||||
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
|
||||
import { ScopedMutator } from 'swr/dist/types'
|
||||
import { ScopedMutator } from 'swr/dist/_internal'
|
||||
import { Label, labelFragment } from '../fragments/labelFragment'
|
||||
import {
|
||||
LibraryItems,
|
||||
|
|
@ -116,7 +116,8 @@ export function useGetArticleQuery({
|
|||
|
||||
const { data, error, mutate } = useSWR(
|
||||
slug ? [query, username, slug, includeFriendsHighlights] : null,
|
||||
makeGqlFetcher(variables)
|
||||
makeGqlFetcher(query, variables),
|
||||
{}
|
||||
)
|
||||
|
||||
let resultData: ArticleData | undefined = data as ArticleData
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export function useGetArticleSavingStatus({
|
|||
`
|
||||
const key = id ? [query, id] : [query, url]
|
||||
// poll twice a second
|
||||
const { data, error } = useSWR(key, makeGqlFetcher({ id, url }), {
|
||||
const { data, error } = useSWR(key, makeGqlFetcher(query, { id, url }), {
|
||||
refreshInterval: 500,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { gql } from "graphql-request"
|
||||
import useSWR from "swr"
|
||||
import { makeGqlFetcher } from "../networkHelpers"
|
||||
import { gql } from 'graphql-request'
|
||||
import useSWR from 'swr'
|
||||
import { makeGqlFetcher } from '../networkHelpers'
|
||||
|
||||
type DiscoverFeedsQueryResponse = {
|
||||
error: any
|
||||
|
|
@ -15,9 +15,9 @@ export type DiscoverFeed = {
|
|||
visibleName: string
|
||||
title: string
|
||||
link: string
|
||||
description?: string,
|
||||
image? : string,
|
||||
type: "rss" | "atom"
|
||||
description?: string
|
||||
image?: string
|
||||
type: 'rss' | 'atom'
|
||||
}
|
||||
|
||||
export function useGetDiscoverFeeds(): DiscoverFeedsQueryResponse {
|
||||
|
|
@ -26,16 +26,16 @@ export function useGetDiscoverFeeds(): DiscoverFeedsQueryResponse {
|
|||
discoverFeeds {
|
||||
... on DiscoverFeedSuccess {
|
||||
feeds {
|
||||
visibleName,
|
||||
id,
|
||||
title,
|
||||
link,
|
||||
description,
|
||||
image,
|
||||
visibleName
|
||||
id
|
||||
title
|
||||
link
|
||||
description
|
||||
image
|
||||
type
|
||||
}
|
||||
}
|
||||
... on DiscoverFeedError{
|
||||
... on DiscoverFeedError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
|
|
@ -44,12 +44,13 @@ export function useGetDiscoverFeeds(): DiscoverFeedsQueryResponse {
|
|||
|
||||
const { data, error, mutate, isValidating } = useSWR(
|
||||
[query],
|
||||
makeGqlFetcher()
|
||||
makeGqlFetcher(query),
|
||||
{}
|
||||
)
|
||||
|
||||
try {
|
||||
if (data) {
|
||||
const result = data as { discoverFeeds: { feeds: DiscoverFeed[] }}
|
||||
const result = data as { discoverFeeds: { feeds: DiscoverFeed[] } }
|
||||
const feeds = result.discoverFeeds.feeds as DiscoverFeed[]
|
||||
return {
|
||||
error,
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ export function useGetHomeItems(): HomeItemResponse {
|
|||
|
||||
const { data, error, isValidating, mutate } = useSWR(
|
||||
[query, variables.first, variables.after],
|
||||
makeGqlFetcher(variables)
|
||||
makeGqlFetcher(query, variables),
|
||||
{}
|
||||
)
|
||||
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ export function useGetIntegrationQuery(name: string): IntegrationQueryResponse {
|
|||
}
|
||||
`
|
||||
|
||||
const { data, mutate, isValidating } = useSWR(query, makeGqlFetcher({ name }))
|
||||
const { data, mutate, isValidating } = useSWR(
|
||||
query,
|
||||
makeGqlFetcher(query, { name }),
|
||||
{}
|
||||
)
|
||||
if (!data) {
|
||||
return {
|
||||
isValidating,
|
||||
|
|
@ -50,7 +54,7 @@ export function useGetIntegrationQuery(name: string): IntegrationQueryResponse {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const result = data as IntegrationQueryResponseData
|
||||
const error = result.integration.errorCodes?.find(() => true)
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -142,13 +142,11 @@ export const recommendationFragment = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export function useGetLibraryItemsQuery({
|
||||
limit,
|
||||
sortDescending,
|
||||
searchQuery,
|
||||
cursor,
|
||||
includeContent = false,
|
||||
}: LibraryItemsQueryInput): LibraryItemsQueryResponse {
|
||||
export function useGetLibraryItemsQuery(
|
||||
folder: string,
|
||||
{ limit, searchQuery, cursor, includeContent = false }: LibraryItemsQueryInput
|
||||
): LibraryItemsQueryResponse {
|
||||
const fullQuery = (`in:${folder} use:folders ` + (searchQuery ?? '')).trim()
|
||||
const query = gql`
|
||||
query Search(
|
||||
$after: String
|
||||
|
|
@ -236,28 +234,27 @@ export function useGetLibraryItemsQuery({
|
|||
const variables = {
|
||||
after: cursor,
|
||||
first: limit,
|
||||
query: searchQuery,
|
||||
query: fullQuery,
|
||||
includeContent,
|
||||
}
|
||||
|
||||
const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(
|
||||
(pageIndex, previousPageData) => {
|
||||
const key = [query, limit, sortDescending, searchQuery, undefined]
|
||||
const key = [query, variables.first, variables.query, undefined]
|
||||
const previousResult = previousPageData as LibraryItemsData
|
||||
|
||||
if (pageIndex === 0) {
|
||||
return key
|
||||
}
|
||||
return [
|
||||
query,
|
||||
limit,
|
||||
sortDescending,
|
||||
searchQuery,
|
||||
pageIndex === 0 ? undefined : previousResult.search.pageInfo.endCursor,
|
||||
]
|
||||
},
|
||||
(_query, _l, _s, _sq, cursor) => {
|
||||
return gqlFetcher(query, { ...variables, after: cursor }, true)
|
||||
(args: any[]) => {
|
||||
const pageIndex = args[4] as number
|
||||
return gqlFetcher(query, { ...variables, after: pageIndex }, true)
|
||||
},
|
||||
{ revalidateFirstPage: false }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
import { gql } from 'graphql-request'
|
||||
import useSWR from 'swr'
|
||||
import { makePublicGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
|
||||
import { Highlight } from '../fragments/highlightFragment'
|
||||
|
||||
type PublicArticleQueryInput = {
|
||||
username: string
|
||||
slug: string
|
||||
selectedHighlightId?: string
|
||||
}
|
||||
|
||||
export type PublicArticleQueryOutput = {
|
||||
publicArticle?: PublicArticleAttributes
|
||||
fetchError: unknown
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
}
|
||||
|
||||
type PublicArticleData = {
|
||||
sharedArticle: NestedPublicArticleData
|
||||
}
|
||||
|
||||
type NestedPublicArticleData = {
|
||||
article: PublicArticleAttributes
|
||||
}
|
||||
|
||||
export type PublicArticleAttributes = {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
url: string
|
||||
author?: string
|
||||
image?: string
|
||||
description?: string
|
||||
hasContent?: boolean
|
||||
highlights: Highlight[]
|
||||
}
|
||||
|
||||
export const PublicArticleGQLFragment = gql`
|
||||
fragment PublicArticle on Article {
|
||||
id
|
||||
title
|
||||
slug
|
||||
url
|
||||
author
|
||||
image
|
||||
description
|
||||
savedByViewer
|
||||
postedByViewer
|
||||
hasContent
|
||||
highlights {
|
||||
id
|
||||
shortId
|
||||
quote
|
||||
prefix
|
||||
suffix
|
||||
patch
|
||||
annotation
|
||||
sharedAt
|
||||
user {
|
||||
id
|
||||
name
|
||||
profile {
|
||||
id
|
||||
username
|
||||
pictureUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const query = gql`
|
||||
query GetPublicArticle(
|
||||
$username: String!
|
||||
$slug: String!
|
||||
$selectedHighlightId: String
|
||||
) {
|
||||
sharedArticle(
|
||||
username: $username
|
||||
slug: $slug
|
||||
selectedHighlightId: $selectedHighlightId
|
||||
) {
|
||||
... on SharedArticleSuccess {
|
||||
article {
|
||||
...PublicArticle
|
||||
}
|
||||
}
|
||||
|
||||
... on SharedArticleError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
${PublicArticleGQLFragment}
|
||||
`
|
||||
|
||||
export function useGetPublicArticleQuery({
|
||||
username,
|
||||
slug,
|
||||
selectedHighlightId,
|
||||
}: PublicArticleQueryInput): PublicArticleQueryOutput {
|
||||
const variables = {
|
||||
username,
|
||||
slug,
|
||||
selectedHighlightId,
|
||||
}
|
||||
|
||||
const { data, error, isValidating } = useSWR(
|
||||
// Only make request if username is defined
|
||||
!!username ? [query, username, slug, selectedHighlightId] : null,
|
||||
makePublicGqlFetcher(variables)
|
||||
)
|
||||
const publicArticle = (data as PublicArticleData)?.sharedArticle?.article
|
||||
|
||||
return {
|
||||
publicArticle,
|
||||
fetchError: error as unknown,
|
||||
isLoading: !error && !publicArticle,
|
||||
isValidating,
|
||||
}
|
||||
}
|
||||
|
||||
export async function publicArticleQuery(
|
||||
context: RequestContext,
|
||||
input: PublicArticleQueryInput
|
||||
): Promise<PublicArticleAttributes> {
|
||||
const result = (await ssrFetcher(context, query, input, false)) as PublicArticleData
|
||||
if (result.sharedArticle.article) {
|
||||
return result.sharedArticle.article
|
||||
}
|
||||
|
||||
return Promise.reject()
|
||||
}
|
||||
|
|
@ -93,7 +93,8 @@ export function useGetSubscriptionsQuery(
|
|||
}
|
||||
const { data, error, mutate, isValidating } = useSWR(
|
||||
[query, variables],
|
||||
makeGqlFetcher(variables)
|
||||
makeGqlFetcher(query, variables),
|
||||
{}
|
||||
)
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,11 @@ export function useGetWebhookQuery(id: string): WebhookQueryResponse {
|
|||
}
|
||||
`
|
||||
|
||||
const { data, mutate, isValidating } = useSWR(query, makeGqlFetcher({ id }))
|
||||
console.log('webhook data', data)
|
||||
const { data, mutate, isValidating } = useSWR(
|
||||
query,
|
||||
makeGqlFetcher(query, { id }),
|
||||
{}
|
||||
)
|
||||
|
||||
try {
|
||||
if (data) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ export function useValidateUsernameQuery({
|
|||
// Don't fetch if username is empty
|
||||
const { data, error, isValidating } = useSWR(
|
||||
username ? [query, username] : null,
|
||||
makePublicGqlFetcher({ username })
|
||||
makePublicGqlFetcher(query, { username }),
|
||||
{}
|
||||
)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { toast, ToastOptions } from 'react-hot-toast'
|
||||
import { CheckCircle, WarningCircle, X } from 'phosphor-react'
|
||||
import { CheckCircle, WarningCircle, X } from '@phosphor-icons/react'
|
||||
import { Box, HStack } from '../components/elements/LayoutPrimitives'
|
||||
import { styled } from '@stitches/react'
|
||||
import { Button } from '../components/elements/Button'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.9",
|
||||
"@google-recaptcha/react": "^1.0.3",
|
||||
"@phosphor-icons/react": "^2.1.5",
|
||||
"@radix-ui/react-avatar": "^0.1.1",
|
||||
"@radix-ui/react-checkbox": "^0.1.5",
|
||||
"@radix-ui/react-dialog": "1.0.5",
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"@sentry/nextjs": "^7.42.0",
|
||||
"@stitches/react": "^1.2.5",
|
||||
"allotment": "^1.20.2",
|
||||
"antd": "4.24.3",
|
||||
"axios": "^1.2.0",
|
||||
"cookie": "^0.5.0",
|
||||
|
|
@ -42,32 +44,29 @@
|
|||
"kbar": "^0.1.0-beta.35",
|
||||
"loadjs": "^4.3.0-rc1",
|
||||
"markdown-it": "^13.0.1",
|
||||
"match-sorter": "^6.3.1",
|
||||
"nanoid": "^3.1.29",
|
||||
"next": "^13.5.6",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"phosphor-react": "^1.4.0",
|
||||
"posthog-js": "^1.78.2",
|
||||
"pspdfkit": "^2023.4.6",
|
||||
"re-resizable": "^6.9.11",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-input-autosize": "^3.0.0",
|
||||
"react-markdown": "^8.0.6",
|
||||
"react-markdown-editor-lite": "^1.3.4",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-sliding-pane": "^7.3.0",
|
||||
"react-spinners": "^0.13.7",
|
||||
"react-super-responsive-table": "^5.2.1",
|
||||
"react-topbar-progress-indicator": "^4.1.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.32.6",
|
||||
"swr": "^1.0.1",
|
||||
"swr": "^2.2.5",
|
||||
"uuid": "^8.3.2",
|
||||
"yet-another-react-lightbox": "^3.12.0"
|
||||
},
|
||||
|
|
@ -109,4 +108,4 @@
|
|||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import { updateTheme } from '../lib/themeUpdater'
|
|||
import { ThemeId } from '../components/tokens/stitches.config'
|
||||
import { posthog } from 'posthog-js'
|
||||
import { GoogleReCaptchaProvider } from '@google-recaptcha/react'
|
||||
import { SWRConfig } from 'swr'
|
||||
|
||||
TopBarProgress.config({
|
||||
barColors: {
|
||||
|
|
|
|||
32
packages/web/pages/home-old.tsx
Normal file
32
packages/web/pages/home-old.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { PrimaryLayout } from '../components/templates/PrimaryLayout'
|
||||
import { HomeFeedContainer } from '../components/templates/homeFeed/HomeFeedContainer'
|
||||
import { VStack } from './../components/elements/LayoutPrimitives'
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
return <LoadedContent />
|
||||
}
|
||||
|
||||
function LoadedContent(): JSX.Element {
|
||||
return (
|
||||
<PrimaryLayout
|
||||
pageMetaDataProps={{
|
||||
title: 'Home - Omnivore',
|
||||
path: '/home',
|
||||
}}
|
||||
pageTestId="home-page-tag"
|
||||
>
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
px: '70px',
|
||||
backgroundColor: '$thLibraryBackground',
|
||||
'@lgDown': { px: '20px' },
|
||||
'@mdDown': { px: '10px' },
|
||||
}}
|
||||
>
|
||||
<HomeFeedContainer />
|
||||
</VStack>
|
||||
</PrimaryLayout>
|
||||
)
|
||||
}
|
||||
55
packages/web/pages/l/[section].tsx
Normal file
55
packages/web/pages/l/[section].tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
|
||||
import {
|
||||
NavigationLayout,
|
||||
NavigationSection,
|
||||
} from '../../components/templates/NavigationLayout'
|
||||
import { HomeContainer } from '../../components/nav-containers/home'
|
||||
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
|
||||
import { useMemo } from 'react'
|
||||
import { HighlightsContainer } from '../../components/nav-containers/highlights'
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const router = useRouter()
|
||||
useApplyLocalTheme()
|
||||
|
||||
const section: NavigationSection | undefined = useMemo(() => {
|
||||
if (!router.isReady) {
|
||||
return undefined
|
||||
}
|
||||
const res = router.query.section
|
||||
if (typeof res !== 'string') {
|
||||
return undefined
|
||||
}
|
||||
return res as NavigationSection
|
||||
}, [router])
|
||||
|
||||
const sectionView = (name: string | string[] | undefined) => {
|
||||
if (typeof name !== 'string') {
|
||||
return <></>
|
||||
}
|
||||
switch (name) {
|
||||
case 'home':
|
||||
return <HomeContainer />
|
||||
case 'highlights':
|
||||
return <HighlightsContainer />
|
||||
case 'library':
|
||||
return <LibraryContainer folder="inbox" />
|
||||
case 'subscriptions':
|
||||
return <LibraryContainer folder="following" />
|
||||
case 'archive':
|
||||
return <LibraryContainer folder="archive" />
|
||||
case 'trash':
|
||||
return <LibraryContainer folder="trash" />
|
||||
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationLayout section={section ?? 'home'}>
|
||||
{sectionView(section)}
|
||||
</NavigationLayout>
|
||||
)
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ export default function Account(): JSX.Element {
|
|||
isUsernameValidationLoading,
|
||||
])
|
||||
|
||||
const { itemsPages, isValidating } = useGetLibraryItemsQuery({
|
||||
const { itemsPages, isValidating } = useGetLibraryItemsQuery('', {
|
||||
limit: 0,
|
||||
searchQuery: 'in:all',
|
||||
sortDescending: false,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { FloppyDisk, Pencil, XCircle } from 'phosphor-react'
|
||||
import { FloppyDisk, Pencil, XCircle } from '@phosphor-icons/react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { FormInput } from '../../../components/elements/FormElements'
|
||||
import { HStack, SpanBox } from '../../../components/elements/LayoutPrimitives'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue