Merge pull request #1884 from omnivore-app/fix/theme-cleanup

Theming cleanup / Add Sepia and Apollo
This commit is contained in:
Jackson Harper 2023-03-13 13:55:28 +08:00 committed by GitHub
commit ea405628c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 440 additions and 580 deletions

View file

@ -130,7 +130,7 @@ export const Button = styled('button', {
ctaModal: {
height: '32px',
verticalAlign: 'middle',
color: '$textDefault',
color: '$thTextContrast',
backgroundColor: '$grayBase',
fontWeight: '600',
padding: '0px 12px',

View file

@ -32,7 +32,7 @@ export function LogoBox(): JSX.Element {
},
}}
>
<OmnivoreNameLogo />
<OmnivoreNameLogo color={theme.colors.thHighContrast.toString()} />
</SpanBox>
</>
)

View file

@ -257,7 +257,7 @@ export default function MobileInstallHelp({
height: 35,
width: 35,
cursor: 'pointer',
backgroundColor: '$tooltipIcons',
backgroundColor: '$labelButtonsBg',
...(selectedTooltip !== item.label && {
filter: 'grayscale(1)',
}),

View file

@ -85,7 +85,7 @@ const textVariants = {
fontWeight: '600',
fontSize: '16px',
lineHeight: '1',
color: '$textDefault',
color: '$thTextContrast',
},
shareHighlightModalAnnotation: {
fontSize: '18px',

View file

@ -11,10 +11,8 @@ import { currentThemeName } from '../../lib/themeUpdater'
import { Check } from 'phosphor-react'
export type HeaderDropdownAction =
| 'apply-darker-theme'
| 'apply-dark-theme'
| 'apply-light-theme'
| 'apply-lighter-theme'
| 'navigate-to-install'
| 'navigate-to-emails'
| 'navigate-to-labels'
@ -49,7 +47,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
css={{ background: '#FFFFFF' }}
data-state={isDark ? 'unselected' : 'selected'}
onClick={() => {
props.actionHandler('apply-lighter-theme')
props.actionHandler('apply-light-theme')
setCurrentTheme(currentThemeName())
}}
>

View file

@ -234,7 +234,7 @@ function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
}}
>
<StyledToggleButton
data-state={currentTheme() != ThemeId.Darker ? 'on' : 'off'}
data-state={currentTheme() != ThemeId.Dark ? 'on' : 'off'}
onClick={() => {
updateTheme(ThemeId.Light)
}}
@ -243,9 +243,9 @@ function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
<Sun size={15} color={theme.colors.thTextContrast2.toString()} />
</StyledToggleButton>
<StyledToggleButton
data-state={currentTheme() == ThemeId.Darker ? 'on' : 'off'}
data-state={currentTheme() == ThemeId.Dark ? 'on' : 'off'}
onClick={() => {
updateTheme(ThemeId.Darker)
updateTheme(ThemeId.Dark)
}}
>
Dark

View file

@ -10,6 +10,7 @@ import { KeyboardShortcutListModal } from './KeyboardShortcutListModal'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
import { setupAnalytics } from '../../lib/analytics'
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
import { applyStoredTheme } from '../../lib/themeUpdater'
type PrimaryLayoutProps = {
children: ReactNode
@ -21,6 +22,8 @@ type PrimaryLayoutProps = {
}
export function PrimaryLayout(props: PrimaryLayoutProps): JSX.Element {
applyStoredTheme(false)
const { viewerData } = useGetViewerQuery()
const router = useRouter()
const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false)
@ -82,32 +85,24 @@ export function PrimaryLayout(props: PrimaryLayoutProps): JSX.Element {
) : null}
<Box
css={{
height: '100%',
width: '100vw',
height: '100vh',
bg: 'transparent',
bg: '$thBackground2',
}}
>
<Box
css={{
height: '100%',
width: '100vw',
bg: '$thBackground2',
}}
>
{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}
</Box>
{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}
</Box>
<div data-testid={props.pageTestId} />
</>

View file

@ -1,14 +1,16 @@
import { Box } from '../../elements/LayoutPrimitives'
import { useReadingProgressAnchor } from '../../../lib/hooks/useReadingProgressAnchor'
import {
getTopOmnivoreAnchorElement,
parseDomTree,
} from '../../../lib/anchorElements'
import {
ScrollOffsetChangeset,
useScrollWatcher,
} from '../../../lib/hooks/useScrollWatcher'
import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
import { MutableRefObject, useEffect, useRef, useState } from 'react'
import { Tweet } from 'react-twitter-widgets'
import { render } from 'react-dom'
import { isDarkTheme } from '../../../lib/themeUpdater'
import debounce from 'lodash/debounce'
import { ArticleMutations } from '../../../lib/articleActions'
export type ArticleProps = {
@ -16,6 +18,7 @@ export type ArticleProps = {
content: string
initialAnchorIndex: number
initialReadingProgress?: number
initialReadingProgressTop?: number
highlightHref: MutableRefObject<string | null>
articleMutations: ArticleMutations
}
@ -27,41 +30,31 @@ export function Article(props: ArticleProps): JSX.Element {
props.initialReadingProgress
)
const [readingAnchorIndex, setReadingAnchorIndex] = useState(
props.initialAnchorIndex
)
const [shouldScrollToInitialPosition, setShouldScrollToInitialPosition] =
useState(true)
const articleContentRef = useRef<HTMLDivElement | null>(null)
useReadingProgressAnchor(articleContentRef, setReadingAnchorIndex)
const debouncedSetReadingProgress = useMemo(
() =>
debounce((readingProgress: number) => {
setReadingProgress(readingProgress)
}, 2000),
[]
)
// Stop the invocation of the debounced function
// after unmounting
useEffect(() => {
return () => {
debouncedSetReadingProgress.cancel()
}
}, [])
const clampToPercent = (float: number) => {
return Math.floor(Math.max(0, Math.min(100, float)))
}
useEffect(() => {
;(async () => {
if (!readingProgress) return
if (!articleContentRef.current) return
if (!window.document.scrollingElement) return
const anchor = getTopOmnivoreAnchorElement(articleContentRef.current)
const topPositionPercent =
window.scrollY / window.document.scrollingElement.scrollHeight
const anchorIndex = Number(anchor)
await props.articleMutations.articleReadingProgressMutation({
id: props.articleId,
// round reading progress to 100% if more than that
readingProgressPercent: readingProgress > 100 ? 100 : readingProgress,
readingProgressAnchorIndex: readingAnchorIndex,
readingProgressPercent: clampToPercent(readingProgress),
readingProgressTopPercent: clampToPercent(topPositionPercent * 100),
readingProgressAnchorIndex:
anchorIndex == Number.NaN ? undefined : anchorIndex,
})
})()
@ -82,13 +75,13 @@ export function Article(props: ArticleProps): JSX.Element {
useScrollWatcher((changeset: ScrollOffsetChangeset) => {
if (window && window.document.scrollingElement) {
const newReadingProgress =
window.scrollY / window.document.scrollingElement.scrollHeight
const adjustedReadingProgress =
newReadingProgress > 0.92 ? 1 : newReadingProgress
debouncedSetReadingProgress(adjustedReadingProgress * 100)
const bottomProgress =
(window.scrollY + window.document.scrollingElement.clientHeight) /
window.document.scrollingElement.scrollHeight
setReadingProgress(bottomProgress * 100)
}
}, 1000)
}, 2500)
// Scroll to initial anchor position
useEffect(() => {
@ -101,6 +94,8 @@ export function Article(props: ArticleProps): JSX.Element {
setShouldScrollToInitialPosition(false)
parseDomTree(articleContentRef.current)
// If we are scrolling to a highlight, dont scroll to read position
if (props.highlightHref.current) {
return

View file

@ -9,17 +9,15 @@ import {
import { theme, ThemeId } from './../../tokens/stitches.config'
import { HighlightsLayer } from '../../templates/article/HighlightsLayer'
import { Button } from '../../elements/Button'
import { useEffect, useState, useRef, useMemo } from 'react'
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'
import { ReportIssuesModal } from './ReportIssuesModal'
import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation'
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
import { updateTheme, updateThemeLocally } from '../../../lib/themeUpdater'
import { ArticleMutations } from '../../../lib/articleActions'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { Recommendation } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { Avatar } from '../../elements/Avatar'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
type ArticleContainerProps = {
article: ArticleAttributes
@ -125,12 +123,14 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
window.location.hash ? window.location.hash.split('#')[1] : null
)
const updateFontSize = async (newFontSize: number) => {
if (fontSize !== newFontSize) {
setFontSize(newFontSize)
await userPersonalizationMutation({ fontSize: newFontSize })
}
}
const updateFontSize = useCallback(
(newFontSize: number) => {
if (fontSize !== newFontSize) {
setFontSize(newFontSize)
}
},
[setFontSize]
)
useEffect(() => {
updateFontSize(props.fontSize ?? 20)
@ -263,7 +263,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
? theme.colors.readerFontHighContrast.toString()
: theme.colors.readerFont.toString(),
readerTableHeaderColor: theme.colors.readerTableHeader.toString(),
readerHeadersColor: theme.colors.readerHeader.toString(),
readerHeadersColor: theme.colors.readerFont.toString(),
}
const recommendationsWithNotes = useMemo(() => {
@ -279,7 +279,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
<Box
id="article-container"
css={{
padding: '16px',
padding: '30px',
paddingTop: '80px',
maxWidth: `${styles.maxWidthPercentage ?? 100}%`,
background: props.isAppleAppEmbed
@ -310,6 +310,10 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
? `${styles.maxWidthPercentage}%`
: 1024 - styles.margin,
},
'@mdDown': {
padding: '15px',
paddingTop: '80px',
},
}}
>
<VStack alignment="start" distribution="start">
@ -325,6 +329,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
fontFamily: styles.fontFamily,
width: '100%',
wordWrap: 'break-word',
color: styles.readerFontColor,
}}
>
{props.article.title}
@ -361,6 +366,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
content={props.article.content}
highlightHref={highlightHref}
initialAnchorIndex={props.article.readingProgressAnchorIndex}
initialReadingProgressTop={props.article.readingProgressTopPercent}
articleMutations={props.articleMutations}
/>
<Button

View file

@ -520,11 +520,6 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
function ThemeSelector(props: ReaderSettingsProps): JSX.Element {
const [currentTheme, setCurrentTheme] = useState(currentThemeName())
const isDark = useMemo(() => {
return currentTheme === 'Dark' || currentTheme === 'Darker'
}, [currentTheme])
return (
<VStack
css={{
@ -563,13 +558,15 @@ function ThemeSelector(props: ReaderSettingsProps): JSX.Element {
border: '2px solid #6A6968',
},
}}
data-state={isDark ? 'unselected' : 'selected'}
data-state={currentTheme == ThemeId.Light ? 'selected' : 'unselected'}
onClick={() => {
updateTheme(ThemeId.Light)
setCurrentTheme(currentThemeName())
}}
>
{!isDark && <Check color="#6A6968" size={15} weight="bold" />}
{currentTheme == ThemeId.Light && (
<Check color="#6A6968" size={15} weight="bold" />
)}
</Button>
<Button
style="themeSwitch"
@ -580,7 +577,7 @@ function ThemeSelector(props: ReaderSettingsProps): JSX.Element {
justifyContent: 'center',
width: '30px',
height: '30px',
background: '#3B3938',
background: '#2A2A2A',
borderRadius: '50%',
border: 'unset',
'&:hover': {
@ -591,13 +588,73 @@ function ThemeSelector(props: ReaderSettingsProps): JSX.Element {
border: '2px solid #6A6968',
},
}}
data-state={isDark ? 'selected' : 'unselected'}
data-state={currentTheme == ThemeId.Dark ? 'selected' : 'unselected'}
onClick={() => {
updateTheme(ThemeId.Dark)
setCurrentTheme(currentThemeName())
}}
>
{isDark && <Check color="#F9D354" size={20} />}
{currentTheme == ThemeId.Dark && <Check color="#F9D354" size={20} />}
</Button>
<Button
style="themeSwitch"
css={{
display: 'flex',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
width: '30px',
height: '30px',
background: '#FBF0D9',
borderRadius: '50%',
border: 'unset',
'&:hover': {
transform: 'scale(1.1)',
border: '2px solid #6A6968',
},
'&[data-state="selected"]': {
border: '2px solid #6A6968',
},
}}
data-state={currentTheme == ThemeId.Sepia ? 'selected' : 'unselected'}
onClick={() => {
updateTheme(ThemeId.Sepia)
setCurrentTheme(currentThemeName())
}}
>
{currentTheme == ThemeId.Sepia && <Check color="#6A6968" size={20} />}
</Button>
<Button
style="themeSwitch"
css={{
display: 'flex',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
width: '30px',
height: '30px',
background: '#6A6968',
borderRadius: '50%',
border: 'unset',
'&:hover': {
transform: 'scale(1.1)',
border: '2px solid #6A6968',
},
'&[data-state="selected"]': {
border: '2px solid #6A6968',
},
}}
data-state={
currentTheme == ThemeId.Apollo ? 'selected' : 'unselected'
}
onClick={() => {
updateTheme(ThemeId.Apollo)
setCurrentTheme(currentThemeName())
}}
>
{currentTheme == ThemeId.Apollo && (
<Check color="#F9D354" size={20} />
)}
</Button>
</HStack>
</VStack>

View file

@ -3,13 +3,18 @@ import {
ModalContent,
ModalOverlay,
} from '../../elements/ModalPrimitives'
import { Box, HStack, StyledLink, VStack } from '../../elements/LayoutPrimitives'
import {
Box,
HStack,
StyledLink,
VStack,
} from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { useCopyLink } from '../../../lib/hooks/useCopyLink'
import { CloseIcon } from '../../elements/images/CloseIcon'
import {OmnivoreLogoIcon} from '../../elements/images/OmnivoreNameLogo'
import { OmnivoreLogoIcon } from '../../elements/images/OmnivoreNameLogo'
import { useState } from 'react'
import { TooltipWrapped } from '../../elements/Tooltip'
import { TwitterLogo, FacebookLogo } from 'phosphor-react'
@ -26,16 +31,14 @@ type ShareModalLayoutProps = {
children: React.ReactNode
}
export function ShareModalLayout(
props: ShareModalLayoutProps
): JSX.Element {
export function ShareModalLayout(props: ShareModalLayoutProps): JSX.Element {
const { copyLink, isLinkCopied } = useCopyLink(props.url, props.type)
const [switchOn, setSwitchOn] = useState(false);
const [switchOn, setSwitchOn] = useState(false)
const toggleSwitch = () => {
setSwitchOn(!switchOn);
setSwitchOn(!switchOn)
}
const iconColor = theme.colors.grayText.toString()
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
<ModalOverlay />
@ -43,15 +46,29 @@ export function ShareModalLayout(
onPointerDownOutside={(event) => {
event.preventDefault()
}}
css={{ overflow: 'auto', p: '0px', border: '1px solid $grayBorder', boxShadow: 'none'}}
css={{
overflow: 'auto',
p: '0px',
border: '1px solid $grayBorder',
boxShadow: 'none',
}}
>
<VStack distribution="start" css={{ p: '0' }}>
<HStack
distribution="between"
alignment="center"
css={{ width: '100%', pt: '24px', pl: '24px', pr: '24px', boxSizing: 'border-box', pb: '16px' }}
css={{
width: '100%',
pt: '24px',
pl: '24px',
pr: '24px',
boxSizing: 'border-box',
pb: '16px',
}}
>
<StyledText style="modalTitle" css={{ p: '0' }}>{props.modalTitle}</StyledText>
<StyledText style="modalTitle" css={{ p: '0' }}>
{props.modalTitle}
</StyledText>
<Button
css={{ p: '0' }}
style="ghost"
@ -59,17 +76,28 @@ export function ShareModalLayout(
props.onOpenChange(false)
}}
>
<CloseIcon
size={24}
strokeColor={iconColor}
/>
<CloseIcon size={24} strokeColor={iconColor} />
</Button>
</HStack>
{props.children}
<HStack
alignment='start'
distribution='start'
css={{ alignItems: 'center', pt: '16px', pb: '16px', pl: '24px', pr: '24px', height:"64px", width: '100%', boxSizing: 'border-box', gap: '8px', mb: '0px', bg: '$grayBg', borderTop: '1px solid $grayBorder', borderRadius: '0px 0px 6px 6px' }}
alignment="start"
distribution="start"
css={{
alignItems: 'center',
pt: '16px',
pb: '16px',
pl: '24px',
pr: '24px',
height: '64px',
width: '100%',
boxSizing: 'border-box',
gap: '8px',
mb: '0px',
bg: '$grayBg',
borderTop: '1px solid $grayBorder',
borderRadius: '0px 0px 6px 6px',
}}
>
<StyledText style="boldText" css={{ m: '0' }}>
Secret URL
@ -78,45 +106,83 @@ export function ShareModalLayout(
tooltipContent="Link copied!"
tooltipSide="top"
active={isLinkCopied}
style={{background: "linear-gradient(0deg, rgba(10, 8, 6, 0.8), rgba(10, 8, 6, 0.8)), #FFFFFF;"}}
arrowStyles={{fill: "linear-gradient(0deg, rgba(10, 8, 6, 0.8), rgba(10, 8, 6, 0.8)), #FFFFFF;"}}
style={{
background:
'linear-gradient(0deg, rgba(10, 8, 6, 0.8), rgba(10, 8, 6, 0.8)), #FFFFFF;',
}}
arrowStyles={{
fill: 'linear-gradient(0deg, rgba(10, 8, 6, 0.8), rgba(10, 8, 6, 0.8)), #FFFFFF;',
}}
>
<button onClick={toggleSwitch} className='track' style={{display:'flex', padding:'2px', flexDirection: `${switchOn ? 'row-reverse' : 'row'}`, alignItems: 'center', width: '40px', height:'24px', background: `${switchOn ? 'rgba(255, 210, 52, 1)' : 'rgba(10, 8, 6, 0.15)'}`, borderRadius: '12px', borderColor: theme.colors.grayBorder.toString(), borderWidth: 1, borderStyle: 'solid',}}>
<div className='thumb' style={{width: '20px', height: '20px', borderRadius: '20px', background: 'rgba(255, 255, 255, 1)', border: '2px solid rgba(0, 0, 0, 0.06)',}}>
</div>
</button>
<button
onClick={toggleSwitch}
className="track"
style={{
display: 'flex',
padding: '2px',
flexDirection: `${switchOn ? 'row-reverse' : 'row'}`,
alignItems: 'center',
width: '40px',
height: '24px',
background: `${
switchOn ? 'rgba(255, 210, 52, 1)' : 'rgba(10, 8, 6, 0.15)'
}`,
borderRadius: '12px',
borderColor: theme.colors.grayBorder.toString(),
borderWidth: 1,
borderStyle: 'solid',
}}
>
<div
className="thumb"
style={{
width: '20px',
height: '20px',
borderRadius: '20px',
background: 'rgba(255, 255, 255, 1)',
border: '2px solid rgba(0, 0, 0, 0.06)',
}}
></div>
</button>
</TooltipWrapped>
{switchOn && <Button style='ctaModal' onClick={copyLink}>
Copy Link
</Button>}
{switchOn && (
<Button style="ctaModal" onClick={copyLink}>
Copy Link
</Button>
)}
<Box css={{
display:'flex',
flexDirection: 'row',
marginLeft: 'auto',
gap: '24px',
alignItems: 'center',
}}>
<Box
css={{
display: 'flex',
flexDirection: 'row',
marginLeft: 'auto',
gap: '24px',
alignItems: 'center',
}}
>
<StyledLink
target='_blank'
target="_blank"
css={{ height: '24px' }}
referrerPolicy='no-referrer'
referrerPolicy="no-referrer"
href={``}
>
<OmnivoreLogoIcon size={26} strokeColor={theme.colors.textNonEssential.toString()} />
<OmnivoreLogoIcon
size={26}
strokeColor={theme.colors.thTextContrast.toString()}
/>
</StyledLink>
<StyledLink
target='_blank'
target="_blank"
css={{ height: '24px' }}
referrerPolicy='no-referrer'
referrerPolicy="no-referrer"
href={`https://www.facebook.com/sharer/sharer.php?u=${props.url}&t=${props.title}&display=page`}
>
<FacebookLogo width={26} height={26} color={iconColor} />
</StyledLink>
<StyledLink
target='_blank'
target="_blank"
css={{ height: '24px' }}
referrerPolicy='no-referrer'
referrerPolicy="no-referrer"
href={`https://twitter.com/intent/tweet?text=${props.title}&url=${props.url}`}
>
<TwitterLogo width={26} height={26} color={iconColor} />

View file

@ -19,7 +19,7 @@ export function SkeletonArticleContainer(
fontFamily: props.fontFamily ?? 'inter',
readerFontColor: theme.colors.readerFont.toString(),
readerTableHeaderColor: theme.colors.readerTableHeader.toString(),
readerHeadersColor: theme.colors.readerHeader.toString(),
readerHeadersColor: theme.colors.readerFont.toString(),
}
return (

View file

@ -34,7 +34,6 @@ import {
} from '../../../lib/networking/fragments/articleFragment'
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import { EditLibraryItemModal } from './EditItemModals'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import debounce from 'lodash/debounce'
import {
SearchItem,
@ -65,8 +64,6 @@ const debouncedFetchSearchResults = debounce((query, cb) => {
}, 300)
export function HomeFeedContainer(): JSX.Element {
useGetUserPreferences()
const { viewerData } = useGetViewerQuery()
const router = useRouter()
const { queryValue } = useKBar((state) => ({ queryValue: state.searchQuery }))

View file

@ -56,6 +56,7 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
<AddLinkButton
showAddLinkModal={() => props.setShowAddLinkModal(true)}
/>
<Box css={{ height: '250px ' }} />
</Box>
{/* This spacer pushes library content to the right of
the fixed left side menu. */}
@ -186,6 +187,7 @@ function Labels(props: LibraryFilterMenuProps): JSX.Element {
<MenuPanel
title="Labels"
editTitle="Edit Labels"
hideBottomBorder={true}
editFunc={() => {
window.location.href = '/settings/labels'
}}
@ -203,6 +205,7 @@ type MenuPanelProps = {
children: ReactNode
editFunc?: () => void
editTitle?: string
hideBottomBorder?: boolean
}
function MenuPanel(props: MenuPanelProps): JSX.Element {
@ -211,7 +214,9 @@ function MenuPanel(props: MenuPanelProps): JSX.Element {
css={{
m: '0px',
width: '100%',
borderBottom: '1px solid $thBorderColor',
borderBottom: props.hideBottomBorder
? '1px solid transparent'
: '1px solid $thBorderColor',
px: '15px',
}}
alignment="start"
@ -422,10 +427,19 @@ function AddLinkButton(props: AddLinkButtonProps): JSX.Element {
<>
<VStack
css={{
marginTop: 'auto',
width: LIBRARY_LEFT_MENU_WIDTH,
height: '80px',
position: 'fixed',
bottom: '0px',
pl: '25px',
height: '80px',
bg: '$thBackground',
width: LIBRARY_LEFT_MENU_WIDTH,
borderTop: '1px solid $thBorderColor',
borderRight: '1px solid $thBorderColor',
'@mdDown': {
width: '100%',
},
}}
distribution="center"
>
@ -436,6 +450,7 @@ function AddLinkButton(props: AddLinkButtonProps): JSX.Element {
pr: '20px',
fontSize: '14px',
verticalAlign: 'center',
color: isDark
? theme.colors.thHighContrast.toString()
: theme.colors.thTextContrast2.toString(),
@ -456,7 +471,6 @@ function AddLinkButton(props: AddLinkButtonProps): JSX.Element {
<SpanBox css={{ width: '10px' }}></SpanBox>Add Link
</Button>
</VStack>
<Box css={{ height: '180px ' }} />
</>
)
}

View file

@ -35,9 +35,12 @@ export function ReaderHeader(props: ReaderHeaderProps): JSX.Element {
'@xlgDown': {
height: MOBILE_HEADER_HEIGHT,
pt: '0px',
bg: '$thBackground3',
bg: '$readerMargin',
borderBottom: '1px solid $thBorderColor',
},
'@mdDown': {
bg: '$readerBg',
},
}}
>
<HStack

View file

@ -2,12 +2,10 @@ import type * as Stitches from '@stitches/react'
import { createStitches, createTheme } from '@stitches/react'
export enum ThemeId {
Lighter = 'White',
Light = 'LightGray',
Dark = 'Gray',
Darker = 'Dark',
Light = 'Light',
Dark = 'Dark',
Sepia = 'Sepia',
Charcoal = 'Charcoal',
Apollo = 'Apollo',
}
export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
@ -119,10 +117,8 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
grayBg: '#FFFFFF',
grayBgActive: '#e6e6e6',
grayBorder: '#F0F0F0',
lightBorder: '#F0F0F0',
grayTextContrast: '#3A3939',
graySolid: '#9C9B9A',
textDefault: 'rgba(255, 255, 255, 0.8)',
utilityTextDefault: '#3B3938',
utilityTextSubtle: 'rgba(255, 255, 255, 0.65)',
textNonessential: 'rgba(10, 8, 6, 0.4)',
@ -132,8 +128,6 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
grayLine: 'hsl(0 0% 88.7%)',
grayBorderHover: 'hsl(0 0% 78.0%)',
grayText: '#6A6968',
graySeparator: '#DADADA',
grayProgressBackground: '#FFFFFF',
// Semantic Colors
highlightBackground: '250, 227, 146',
@ -145,8 +139,6 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
// Brand Colors
omnivoreRed: '#FA5E4A;',
omnivoreGray: '#3D3D3D',
omnivoreOrange: '#FF9B3E',
omnivorePeach: 'rgb(255, 212, 146)',
omnivoreYellow: 'rgb(255, 234, 159)',
omnivoreLightGray: 'rgb(125, 125, 125)',
omnivoreCtaYellow: 'rgb(255, 210, 52)',
@ -155,24 +147,19 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
readerBg: 'white',
readerFont: '#3D3D3D',
readerFontHighContrast: 'black',
readerFontTransparent: 'rgba(61,61,61,0.65)',
readerHeader: '3D3D3D',
readerTableHeader: '#FFFFFF',
readerMargin: 'white',
// Avatar Fallback color
avatarBg: '#FFEA9F',
avatarFont: '#9C7C0A',
labelButtonsBg: '#F5F5F4',
tooltipIcons: '#FDFAEC',
textSubtle: '#605F5D',
libraryBackground: '#FFFFFF',
libraryActiveMenuItem: '#F8F8F8',
border: '#F0F0F0',
//utility
textNonEssential: 'rgba(10, 8, 6, 0.4)',
overlay: 'rgba(63, 62, 60, 0.2)',
// New theme, special naming to keep things straigh
@ -181,6 +168,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
thBackground2: '#F3F3F3',
thBackground3: '#FFFFFF',
thBackground4: '#EBEBEB',
thBackgroundActive: '#F9F9F9',
thBackgroundContrast: '#FFFFFF',
thTextContrast: '#1E1E1E',
@ -223,7 +211,6 @@ const darkThemeSpec = {
grayTextContrast: '#D8D7D7',
grayBorder: '#323232',
graySolid: '#9C9B9A',
textDefault: 'rgba(10, 8, 6, 0.8)',
utilityTextDefault: '#CDCDCD',
textNonessential: 'rgba(97, 97, 97, 1)',
@ -232,8 +219,6 @@ const darkThemeSpec = {
grayLine: 'hsl(0 0% 19.9%)',
grayBorderHover: 'hsl(0 0% 31.2%)',
grayText: '#CDCDCD',
graySeparator: '#323232',
grayProgressBackground: '#616161',
// Semantic Colors
highlightBackground: '134, 119, 64',
@ -246,20 +231,17 @@ const darkThemeSpec = {
readerBg: '#303030',
readerFont: '#b9b9b9',
readerFontHighContrast: 'white',
readerHeader: '#b9b9b9',
readerTableHeader: '#FFFFFF',
tooltipIcons: '#5F5E58',
readerMargin: '#2A2A2A',
avatarBg: '#7B5C3E',
avatarFont: '#D9D9D9',
textSubtle: '#AAAAAA',
libraryBackground: '#252525',
libraryActiveMenuItem: '#3B3938',
border: '#323232',
//utility
utilityTextSubtle: 'rgba(255, 255, 255, 0.65)',
textNonEssential: 'rgba(10, 8, 6, 0.4)',
overlay: 'rgba(10, 8, 6, 0.65)',
labelButtonsBg: '#5F5E58',
@ -272,7 +254,7 @@ const darkThemeSpec = {
thBackground2: '#3D3D3D',
thBackground3: '#242424',
thBackground4: '#3D3D3D',
thBackgroundActive: '#2A2A2B',
thBackgroundActive: '#2E2E2E',
thBackgroundContrast: '#000000',
thTextContrast: '#FFFFFF',
@ -297,43 +279,37 @@ const darkThemeSpec = {
const sepiaThemeSpec = {
colors: {
// Reader Colors
readerBg: '#F9F1DC',
readerFont: '#554A34',
readerFontHighContrast: 'black',
readerHeader: '554A34',
readerBg: '#FBF0D9',
readerFont: '#5F4B32',
readerMargin: '#F3F3F3',
readerFontHighContrast: '#0A0806',
readerTableHeader: '#FFFFFF',
},
}
const charcoalThemeSpec = {
const apolloThemeSpec = {
colors: {
// Reader Colors
readerBg: '#303030',
readerFont: '#b9b9b9',
readerBg: '#6A6968',
readerFont: '#F3F3F3',
readerMargin: '#474747',
readerFontHighContrast: 'white',
readerHeader: '#b9b9b9',
readerTableHeader: '#FFFFFF',
},
}
// Dark and Darker theme now match each other.
// Use the darkThemeSpec object to make updates.
export const darkTheme = createTheme(ThemeId.Dark, darkThemeSpec)
export const darkerTheme = createTheme(ThemeId.Darker, darkThemeSpec)
export const sepiaTheme = createTheme(ThemeId.Sepia, {
...darkThemeSpec,
...sepiaThemeSpec,
})
export const charcoalTheme = createTheme(ThemeId.Charcoal, {
export const apolloTheme = createTheme(ThemeId.Apollo, {
...darkThemeSpec,
...charcoalThemeSpec,
colors: {
...darkThemeSpec.colors,
...apolloThemeSpec.colors,
},
})
// Lighter theme now matches the default theme.
// This only exists for users that might still have a lighter theme set
export const lighterTheme = createTheme(ThemeId.Lighter, {})
// Apply global styles in here
export const globalStyles = globalCss({
body: {

View file

@ -0,0 +1,69 @@
const ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES = [
'omnivore-highlight-id',
'data-twitter-tweet-id',
'data-instagram-id',
]
// We search in reverse so we can find the last element
// that is visible on the page
export const getTopOmnivoreAnchorElement = (
articleContentElement: HTMLElement
): string | undefined => {
let topVisibleRect: Element | undefined = undefined
const anchors = Array.from(
document.querySelectorAll(`[data-omnivore-anchor-idx]`)
).reverse()
for (const anchor of anchors) {
const rect = anchor.getBoundingClientRect()
if (rect.top >= 0 && rect.bottom <= articleContentElement.clientHeight) {
if (
topVisibleRect &&
topVisibleRect.getBoundingClientRect().top < rect.top
) {
continue
}
topVisibleRect = anchor
}
}
return topVisibleRect?.getAttribute(`data-omnivore-anchor-idx`) ?? undefined
}
export function parseDomTree(
pageNode: HTMLDivElement | null
): HTMLDivElement[] {
if (!pageNode || pageNode.childNodes.length == 0) {
return []
}
const nodesToVisitStack: [HTMLDivElement] = [pageNode]
const visitedNodeList = []
while (nodesToVisitStack.length > 0) {
const currentNode = nodesToVisitStack.pop()
if (
currentNode?.nodeType !== Node.ELEMENT_NODE ||
// Avoiding dynamic elements from being counted as anchor-allowed elements
ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES.some((attrib) =>
currentNode.hasAttribute(attrib)
)
) {
continue
}
visitedNodeList.push(currentNode)
;[].slice
.call(currentNode.childNodes)
.reverse()
.forEach(function (node) {
nodesToVisitStack.push(node)
})
}
visitedNodeList.shift()
visitedNodeList.forEach((node, index) => {
// start from index 1, index 0 reserved for anchor unknown.
node.setAttribute('data-omnivore-anchor-idx', (index + 1).toString())
})
return visitedNodeList
}

View file

@ -1,16 +1,11 @@
import { useRegisterActions } from 'kbar'
import { useCallback, useState } from 'react'
import { userPersonalizationMutation } from '../networking/mutations/userPersonalizationMutation'
import {
useGetUserPreferences,
UserPreferences,
} from '../networking/queries/useGetUserPreferences'
import { applyStoredTheme } from '../themeUpdater'
import { usePersistedState } from './usePersistedState'
const DEFAULT_FONT = 'Inter'
export type ReaderSettings = {
preferencesData: UserPreferences | undefined
fontSize: number
lineHeight: number
marginWidth: number
@ -41,12 +36,13 @@ export type ReaderSettings = {
}
export const useReaderSettings = (): ReaderSettings => {
const { preferencesData } = useGetUserPreferences()
applyStoredTheme(false)
const [, updateState] = useState({})
const [fontSize, setFontSize] = usePersistedState({
key: 'fontSize',
initialValue: preferencesData?.fontSize ?? 20,
initialValue: 20,
})
const [lineHeight, setLineHeight] = usePersistedState({
key: 'lineHeight',
@ -76,12 +72,12 @@ export const useReaderSettings = (): ReaderSettings => {
useState(false)
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false)
const updateFontSize = async (newFontSize: number) => {
setFontSize(newFontSize)
;(async () => {
await userPersonalizationMutation({ fontSize: newFontSize })
})()
}
const updateFontSize = useCallback(
(newFontSize: number) => {
setFontSize(newFontSize)
},
[setFontSize]
)
// const [hideMargins, setHideMargins] = usePersistedState<boolean | undefined>({
// key: `--display-hide-margins`,
@ -207,7 +203,6 @@ export const useReaderSettings = (): ReaderSettings => {
)
return {
preferencesData,
fontSize,
lineHeight,
marginWidth,

View file

@ -1,124 +0,0 @@
import { useEffect } from 'react'
const ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES = [
'omnivore-highlight-id',
'data-twitter-tweet-id',
'data-instagram-id',
]
export const useReadingProgressAnchor = (
articleContentRef: React.MutableRefObject<HTMLDivElement | null>,
setReadingAnchorIndex: React.Dispatch<React.SetStateAction<number>>
): void => {
useEffect(() => {
const visitedNodeList = parseDomTree(articleContentRef.current)
const observerOptions = {
root: null,
rootMargin: '0px',
// we only track elements on becoming completely visible.
threshold: [1],
}
function intersectionCallback(entries: IntersectionObserverEntry[]): void {
let topIntersectingElemId = 0
let minTopElem = 100000
entries.forEach(function (entry: IntersectionObserverEntry) {
const elem = entry.target
const elemId = elem.getAttribute('data-omnivore-anchor-idx') || '0'
if (entry.isIntersecting && entry.intersectionRatio === 1) {
// Among all intersecting elements, find the topmost element.
if (entry.boundingClientRect.top < minTopElem) {
minTopElem = entry.boundingClientRect.top
topIntersectingElemId = parseInt(elemId)
}
}
})
if (topIntersectingElemId > 0) {
/*
* Intersection observer is great in finding us the last element on the
* page that becomes visible on scroll. But for better user experience
* we are interested in the topmost element visible on the page that we
* can scroll to at the top on the next article page reader view. We
* iterate in reverse over anchor elements here us to find the topmost
* visible element.
*/
let topVisibleElemId = topIntersectingElemId
while (topVisibleElemId - 1 > 0) {
const elem = document.querySelector(
`[data-omnivore-anchor-idx='${(topVisibleElemId - 1).toString()}']`
)
if (elem) {
const rect = elem.getBoundingClientRect()
if (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <=
(window.innerWidth || document.documentElement.clientWidth)
) {
/* Is Visible */
topVisibleElemId = topVisibleElemId - 1
} else {
break
}
} else {
// Prevents the Event loop from the eternal blocking
throw new Error('Unable to find previous intersection element!')
}
}
setReadingAnchorIndex(topVisibleElemId)
}
}
const nodeObserver = new IntersectionObserver(
intersectionCallback,
observerOptions
)
visitedNodeList?.forEach((elem) => {
nodeObserver.observe(elem)
})
return () => {
nodeObserver.disconnect()
}
}, [articleContentRef, setReadingAnchorIndex])
}
function parseDomTree(pageNode: HTMLDivElement | null): HTMLDivElement[] {
if (!pageNode || pageNode.childNodes.length == 0) {
return []
}
const nodesToVisitStack: [HTMLDivElement] = [pageNode]
const visitedNodeList = []
while (nodesToVisitStack.length > 0) {
const currentNode = nodesToVisitStack.pop()
if (
currentNode?.nodeType !== Node.ELEMENT_NODE ||
// Avoiding dynamic elements from being counted as anchor-allowed elements
ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES.some((attrib) =>
currentNode.hasAttribute(attrib)
)
) {
continue
}
visitedNodeList.push(currentNode)
;[].slice
.call(currentNode.childNodes)
.reverse()
.forEach(function (node) {
nodesToVisitStack.push(node)
})
}
visitedNodeList.shift()
visitedNodeList.forEach((node, index) => {
// start from index 1, index 0 reserved for anchor unknown.
node.setAttribute('data-omnivore-anchor-idx', (index + 1).toString())
})
return visitedNodeList
}

View file

@ -37,6 +37,11 @@ export function useScrollWatcher(effect: Effect, delay: number): void {
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
return () => {
if (throttleTimeout.current) {
clearTimeout(throttleTimeout.current)
}
window.removeEventListener('scroll', handleScroll)
}
}, [currentOffset, delay, effect])
}

View file

@ -13,6 +13,7 @@ export const articleFragment = gql`
contentReader
originalArticleUrl
readingProgressPercent
readingProgressTopPercent
readingProgressAnchorIndex
slug
isArchived
@ -52,6 +53,7 @@ export type ArticleFragmentData = {
contentReader?: ContentReader
originalArticleUrl: string
readingProgressPercent: number
readingProgressTopPercent?: number
readingProgressAnchorIndex: number
slug: string
isArchived: boolean

View file

@ -3,8 +3,9 @@ import { gqlFetcher } from '../networkHelpers'
export type ArticleReadingProgressMutationInput = {
id: string
readingProgressPercent: number
readingProgressAnchorIndex: number
readingProgressPercent?: number
readingProgressTopPercent?: number
readingProgressAnchorIndex?: number
}
export async function articleReadingProgressMutation(

View file

@ -1,61 +0,0 @@
import { gql } from 'graphql-request'
import {
UserPreferences,
SortOrder,
updateUserPreferencesCache,
} from '../queries/useGetUserPreferences'
import { gqlFetcher } from '../networkHelpers'
type UserPersonalizationInput = {
theme?: string
fontSize?: number
fontFamily?: string
margin?: number
libraryLayoutType?: string
librarySortOrder?: SortOrder
}
type SetUserPersonalizationResult = {
setUserPersonalization: InnerSetUserPersonalization
}
type InnerSetUserPersonalization = {
updatedUserPersonalization?: UserPreferences
}
export async function userPersonalizationMutation(
input: UserPersonalizationInput
): Promise<UserPreferences | undefined> {
const mutation = gql`
mutation SetUserPersonalization($input: SetUserPersonalizationInput!) {
setUserPersonalization(input: $input) {
... on SetUserPersonalizationSuccess {
updatedUserPersonalization {
id
theme
fontSize
fontFamily
margin
libraryLayoutType
librarySortOrder
}
}
... on SetUserPersonalizationError {
errorCodes
}
}
}
`
try {
const data = await gqlFetcher(mutation, { input })
const result = data as SetUserPersonalizationResult | undefined
if (result?.setUserPersonalization?.updatedUserPersonalization) {
updateUserPreferencesCache(result.setUserPersonalization.updatedUserPersonalization)
return result.setUserPersonalization?.updatedUserPersonalization
}
return undefined
} catch {
return undefined
}
}

View file

@ -11,7 +11,7 @@ export async function searchQuery({
limit = 10,
searchQuery,
}: LibraryItemsQueryInput): Promise<LibraryItemsData | undefined> {
const query = gql`
const query = gql`
query Search($after: String, $first: Int, $query: String) {
search(first: $first, after: $after, query: $query) {
... on SearchSuccess {
@ -27,6 +27,7 @@ export async function searchQuery({
createdAt
isArchived
readingProgressPercent
readingProgressTopPercent
readingProgressAnchorIndex
author
image
@ -71,8 +72,8 @@ export async function searchQuery({
}
try {
const data = (await gqlFetcher(query, {...variables}))
return data as LibraryItemsData || undefined;
const data = await gqlFetcher(query, { ...variables })
return (data as LibraryItemsData) || undefined
} catch (error) {
console.log('search error', error)
return undefined

View file

@ -50,6 +50,7 @@ export type ArticleAttributes = {
description?: string
contentReader: ContentReader
readingProgressPercent: number
readingProgressTopPercent?: number
readingProgressAnchorIndex: number
slug: string
savedByViewer?: boolean

View file

@ -67,6 +67,7 @@ export type LibraryItemNode = {
contentReader?: ContentReader
originalArticleUrl: string
readingProgressPercent: number
readingProgressTopPercent?: number
readingProgressAnchorIndex: number
slug: string
isArchived: boolean
@ -149,6 +150,7 @@ export function useGetLibraryItemsQuery({
createdAt
isArchived
readingProgressPercent
readingProgressTopPercent
readingProgressAnchorIndex
author
image
@ -349,11 +351,13 @@ export function useGetLibraryItemsQuery({
node: {
...item.node,
readingProgressPercent: 100,
readingProgressTopPercent: 100,
},
})
articleReadingProgressMutation({
id: item.node.id,
readingProgressPercent: 100,
readingProgressTopPercent: 100,
readingProgressAnchorIndex: 0,
})
break
@ -363,11 +367,14 @@ export function useGetLibraryItemsQuery({
node: {
...item.node,
readingProgressPercent: 0,
readingProgressTopPercent: 0,
readingProgressAnchorIndex: 0,
},
})
articleReadingProgressMutation({
id: item.node.id,
readingProgressPercent: 0,
readingProgressTopPercent: 0,
readingProgressAnchorIndex: 0,
})
break

View file

@ -1,88 +0,0 @@
import { gql } from 'graphql-request'
import useSWR, { mutate } from 'swr'
import { gqlFetcher } from '../networkHelpers'
import { applyStoredTheme, updateThemeLocally } from '../../themeUpdater'
import { ThemeId } from '../../../components/tokens/stitches.config'
type UserPreferencesResponse = {
preferencesData?: UserPreferences
preferencesDataError?: unknown
isLoading: boolean
isValidating: boolean
}
type QueryResponse = {
getUserPersonalization: InnerQueryReponse
}
type InnerQueryReponse = {
userPersonalization: UserPreferences
}
export type UserPreferences = {
id: string
theme: string
fontSize: number
fontFamily: string
margin: number
lineHeight?: number
libraryLayoutType: string
librarySortOrder?: SortOrder
}
export type SortOrder = 'ASCENDING' | 'DESCENDING'
const QUERY = gql`
query GetUserPersonalization {
getUserPersonalization {
... on GetUserPersonalizationSuccess {
userPersonalization {
id
theme
margin
fontSize
fontFamily
libraryLayoutType
librarySortOrder
}
}
... on GetUserPersonalizationError {
errorCodes
}
}
}
`
export function updateUserPreferencesCache(
userPersonalization: UserPreferences
): void {
mutate(
QUERY,
{
getUserPersonalization: { userPersonalization },
},
false
)
}
export function useGetUserPreferences(): UserPreferencesResponse {
const currentTheme = applyStoredTheme(false)
const { data, error, isValidating } = useSWR(QUERY, gqlFetcher, {
dedupingInterval: 200000,
})
const preferencesData = (data as QueryResponse | undefined)
?.getUserPersonalization.userPersonalization
const serverThemeKey = preferencesData?.theme as ThemeId | undefined
if (!isValidating && serverThemeKey && currentTheme !== serverThemeKey) {
updateThemeLocally(serverThemeKey)
}
return {
preferencesData,
isValidating,
preferencesDataError: error, // TODO: figure out error possibilities
isLoading: !error && !data,
}
}

View file

@ -1,20 +1,38 @@
import {
ThemeId,
lighterTheme,
darkTheme,
darkerTheme,
sepiaTheme,
apolloTheme,
} from '../components/tokens/stitches.config'
import { userPersonalizationMutation } from './networking/mutations/userPersonalizationMutation'
const themeKey = 'theme'
// Map legacy theme names to their new equivelents
const LEGACY_THEMES: { [string: string]: string } = {
White: ThemeId.Light,
LightGray: ThemeId.Light,
Gray: ThemeId.Dark,
Darker: ThemeId.Dark,
}
export function updateTheme(themeId: string): void {
if (typeof window === 'undefined') {
return
}
updateThemeLocally(themeId)
userPersonalizationMutation({ theme: themeId })
}
function getTheme(themeId: string) {
switch (currentTheme()) {
case ThemeId.Dark:
return darkTheme
case ThemeId.Sepia:
return sepiaTheme
case ThemeId.Apollo:
return apolloTheme
}
return ThemeId.Light
}
export function updateThemeLocally(themeId: string): void {
@ -23,17 +41,13 @@ export function updateThemeLocally(themeId: string): void {
}
document.body.classList.remove(
lighterTheme,
...Object.keys(LEGACY_THEMES),
sepiaTheme,
darkTheme,
darkerTheme,
ThemeId.Light,
ThemeId.Dark,
ThemeId.Darker,
ThemeId.Lighter,
ThemeId.Sepia,
ThemeId.Charcoal
apolloTheme,
...Object.keys(ThemeId)
)
document.body.classList.add(themeId)
document.body.classList.add(getTheme(themeId))
}
export function currentThemeName(): string {
@ -42,17 +56,12 @@ export function currentThemeName(): string {
return 'Light'
case ThemeId.Dark:
return 'Dark'
case ThemeId.Darker:
return 'Darker'
case ThemeId.Lighter:
return 'Lighter'
case ThemeId.Sepia:
return 'Sepia'
case ThemeId.Charcoal:
return 'Charcoal'
default:
return ''
case ThemeId.Apollo:
return 'Apollo'
}
return 'Light'
}
export function currentTheme(): ThemeId | undefined {
@ -60,7 +69,16 @@ export function currentTheme(): ThemeId | undefined {
return undefined
}
return window.localStorage.getItem(themeKey) as ThemeId | undefined
const str = window.localStorage.getItem(themeKey)
if (str && Object.values(ThemeId).includes(str as ThemeId)) {
return str as ThemeId
}
if (str && Object.keys(LEGACY_THEMES).includes(str)) {
return LEGACY_THEMES[str] as ThemeId
}
return ThemeId.Light
}
export function applyStoredTheme(syncWithServer = true): ThemeId | undefined {
@ -72,7 +90,7 @@ export function applyStoredTheme(syncWithServer = true): ThemeId | undefined {
| ThemeId
| undefined
if (theme && Object.values(ThemeId).includes(theme)) {
syncWithServer ? updateTheme(theme) : updateThemeLocally(theme)
updateThemeLocally(theme)
}
return theme
}
@ -81,35 +99,3 @@ export function isDarkTheme(): boolean {
const currentTheme = currentThemeName()
return currentTheme === 'Dark' || currentTheme === 'Darker'
}
export function darkenTheme(): void {
switch (currentTheme()) {
case ThemeId.Dark:
updateTheme(ThemeId.Darker)
break
case ThemeId.Light:
updateTheme(ThemeId.Dark)
break
case ThemeId.Lighter:
updateTheme(ThemeId.Light)
break
default:
break
}
}
export function lightenTheme(): void {
switch (currentTheme()) {
case ThemeId.Dark:
updateTheme(ThemeId.Light)
break
case ThemeId.Darker:
updateTheme(ThemeId.Dark)
break
case ThemeId.Light:
updateTheme(ThemeId.Lighter)
break
default:
break
}
}

View file

@ -47,7 +47,6 @@ const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
export default function Home(): JSX.Element {
const router = useRouter()
const { cache, mutate } = useSWRConfig()
const scrollRef = useRef<HTMLDivElement | null>(null)
const { slug } = router.query
const [showEditModal, setShowEditModal] = useState(false)
@ -355,12 +354,12 @@ export default function Home(): JSX.Element {
<VStack
alignment="center"
distribution="start"
ref={scrollRef}
className="disable-webkit-callout"
css={{
width: '100%',
height: '100%',
background: '$thBackground',
background: '$readerMargin',
overflow: 'scroll',
}}
>
{article && viewerData?.me ? (

View file

@ -24,7 +24,8 @@ import {
KBarResultsComponents,
searchStyle,
} from '../components/elements/KBar'
import { darkenTheme, lightenTheme } from '../lib/themeUpdater'
import { updateTheme } from '../lib/themeUpdater'
import { ThemeId } from '../components/tokens/stitches.config'
TopBarProgress.config({
barColors: {
@ -52,7 +53,7 @@ const generateActions = (router: NextRouter) => {
shortcut: ['v', 'l'],
keywords: 'light theme',
priority: Priority.LOW,
perform: () => lightenTheme(),
perform: () => updateTheme(ThemeId.Light),
},
{
id: 'darkTheme',
@ -61,7 +62,7 @@ const generateActions = (router: NextRouter) => {
shortcut: ['v', 'd'],
keywords: 'dark theme',
priority: Priority.LOW,
perform: () => darkenTheme(),
perform: () => updateTheme(ThemeId.Dark),
},
]

View file

@ -5,40 +5,6 @@ import { getCssText, globalStyles } from '../components/tokens/stitches.config'
export default class Document extends NextDocument {
render() {
const setUserPreferences = `
function getCookie(cname) {
let name = cname + "=";
let ca = document.cookie.split(';');
for(let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function storeCookieInLocalStorage(key) {
let value = getCookie(key);
if (value != "") {
window.localStorage.setItem(key, value)
}
}
storeCookieInLocalStorage("authToken")
storeCookieInLocalStorage("theme")
var themeId = window.localStorage.getItem('theme')
if (themeId) {
document.body.classList.remove('theme-default', 'White', 'Gray', 'LightGray', 'Dark', 'Sepia', 'Charcoal')
document.body.classList.add(themeId)
}
`
globalStyles()
return (
@ -112,7 +78,6 @@ export default class Document extends NextDocument {
<body>
<Main />
<NextScript />
<script dangerouslySetInnerHTML={{ __html: setUserPreferences }} />
</body>
</Html>
)

View file

@ -73,13 +73,7 @@ function AppArticleEmbedContent(
if (articleData) {
return (
<Box
css={{
overflowY: 'auto',
height: '100%',
width: '100vw',
}}
>
<Box>
<Script async src="/static/scripts/mathJaxConfiguration.js" />
<Script
async