Merge pull request #4040 from omnivore-app/feat/web-layout-navigation

Update layout navigation with left menu
This commit is contained in:
Jackson Harper 2024-06-12 20:02:42 +08:00 committed by GitHub
commit e9f3009111
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
113 changed files with 4045 additions and 1467 deletions

View file

@ -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
}

View 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
}

View file

@ -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())

View file

@ -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,
}
}),
},
]
}

View 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;

View file

@ -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;

View file

@ -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'

View file

@ -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',

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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 = {

View file

@ -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'

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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',

View file

@ -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'

View 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 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>
)
}
}

View file

@ -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>

View 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>
)
}
}

View 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>
)
}
}

View 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 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>
)
}
}

View 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 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>
)
}
}

View 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>
)
}
}

View file

@ -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

View file

@ -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} />
)
}

View 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>
)
}

View file

@ -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>
)
}

View file

@ -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'

View file

@ -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'

View file

@ -31,4 +31,6 @@ export type LinkedItemCardProps = {
isHovered?: boolean
isLoading?: boolean
legacyLayout?: boolean
}

View file

@ -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',

View file

@ -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"

View file

@ -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'

View file

@ -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"

View file

@ -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'

View file

@ -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'

View file

@ -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'

View 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>
)
}

View file

@ -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"

View file

@ -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

View file

@ -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'

View file

@ -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,
})

View file

@ -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',
})

View file

@ -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'

View file

@ -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 ?? [])

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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>
)

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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>
)

View file

@ -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)

View file

@ -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={{

View file

@ -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'

View file

@ -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[]) => {

View file

@ -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[],

View file

@ -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}

View file

@ -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,

View file

@ -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()
)

View file

@ -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 = {

View file

@ -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'

File diff suppressed because it is too large Load diff

View 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>
)
}

View file

@ -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>
)
}

View 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>
}

View file

@ -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',

View file

@ -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,

View file

@ -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

View file

@ -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()

View file

@ -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'

View file

@ -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>
</>
)

View file

@ -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'

View file

@ -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',
},
}

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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,
})

View file

@ -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,

View file

@ -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) {

View file

@ -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) {

View file

@ -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 }
)

View file

@ -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()
}

View file

@ -93,7 +93,8 @@ export function useGetSubscriptionsQuery(
}
const { data, error, mutate, isValidating } = useSWR(
[query, variables],
makeGqlFetcher(variables)
makeGqlFetcher(query, variables),
{}
)
try {

View file

@ -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) {

View file

@ -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

View file

@ -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'

View file

@ -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"
}
}
}

View file

@ -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: {

View 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>
)
}

View 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>
)
}

View file

@ -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,

View file

@ -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