mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1884 from omnivore-app/fix/theme-cleanup
Theming cleanup / Add Sepia and Apollo
This commit is contained in:
commit
ea405628c8
32 changed files with 440 additions and 580 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function LogoBox(): JSX.Element {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<OmnivoreNameLogo />
|
||||
<OmnivoreNameLogo color={theme.colors.thHighContrast.toString()} />
|
||||
</SpanBox>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export default function MobileInstallHelp({
|
|||
height: 35,
|
||||
width: 35,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '$tooltipIcons',
|
||||
backgroundColor: '$labelButtonsBg',
|
||||
...(selectedTooltip !== item.label && {
|
||||
filter: 'grayscale(1)',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const textVariants = {
|
|||
fontWeight: '600',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1',
|
||||
color: '$textDefault',
|
||||
color: '$thTextContrast',
|
||||
},
|
||||
shareHighlightModalAnnotation: {
|
||||
fontSize: '18px',
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
|
|
|
|||
|
|
@ -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 ' }} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
69
packages/web/lib/anchorElements.ts
Normal file
69
packages/web/lib/anchorElements.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export type ArticleAttributes = {
|
|||
description?: string
|
||||
contentReader: ContentReader
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressAnchorIndex: number
|
||||
slug: string
|
||||
savedByViewer?: boolean
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue