mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1944 from omnivore-app/feat/notebook-improvements
Notebook improvements
This commit is contained in:
commit
e15119e808
28 changed files with 2365 additions and 958 deletions
|
|
@ -1,91 +0,0 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
|
||||
import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { Button } from './Button'
|
||||
import { HStack, VStack } from './LayoutPrimitives'
|
||||
import { StyledTextArea } from './StyledTextArea'
|
||||
|
||||
type HighlightNoteTextEditAreaProps = {
|
||||
setIsEditing: (editing: boolean) => void
|
||||
highlight: Highlight
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
}
|
||||
|
||||
export const HighlightNoteTextEditArea = (
|
||||
props: HighlightNoteTextEditAreaProps
|
||||
): JSX.Element => {
|
||||
const [noteContent, setNoteContent] = useState(
|
||||
props.highlight.annotation ?? ''
|
||||
)
|
||||
|
||||
const handleNoteContentChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
||||
setNoteContent(event.target.value)
|
||||
},
|
||||
[setNoteContent]
|
||||
)
|
||||
|
||||
return (
|
||||
<VStack css={{ width: '100%' }} key="textEditor">
|
||||
<StyledTextArea
|
||||
css={{
|
||||
my: '10px',
|
||||
minHeight: '200px',
|
||||
borderRadius: '5px',
|
||||
p: '10px',
|
||||
width: '100%',
|
||||
marginTop: '16px',
|
||||
resize: 'vertical',
|
||||
bg: '#EBEBEB',
|
||||
color: '#3D3D3D',
|
||||
}}
|
||||
autoFocus
|
||||
maxLength={4000}
|
||||
value={noteContent}
|
||||
placeholder={'Add your notes...'}
|
||||
onChange={handleNoteContentChange}
|
||||
/>
|
||||
<HStack alignment="center" distribution="end" css={{ width: '100%' }}>
|
||||
<Button
|
||||
style="cancelGeneric"
|
||||
css={{ mr: '$2' }}
|
||||
onClick={() => {
|
||||
props.setIsEditing(false)
|
||||
setNoteContent(props.highlight.annotation ?? '')
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
style="ctaDarkYellow"
|
||||
onClick={async (e) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
const result = await updateHighlightMutation({
|
||||
highlightId: props.highlight.id,
|
||||
annotation: noteContent,
|
||||
})
|
||||
console.log('result: ' + result)
|
||||
|
||||
if (!result) {
|
||||
showErrorToast('There was an error updating your highlight.')
|
||||
} else {
|
||||
showSuccessToast('Note saved')
|
||||
props.highlight.annotation = noteContent
|
||||
props.updateHighlight(props.highlight)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('error updating annoation', err)
|
||||
showErrorToast('There was an error updating your highlight.')
|
||||
}
|
||||
|
||||
props.setIsEditing(false)
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
443
packages/web/components/patterns/HighlightNotes.tsx
Normal file
443
packages/web/components/patterns/HighlightNotes.tsx
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
/* eslint-disable react/no-children-prop */
|
||||
import {
|
||||
ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { formattedShortTime } from '../../lib/dateFormatting'
|
||||
import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
|
||||
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import MdEditor from 'react-markdown-editor-lite'
|
||||
import 'react-markdown-editor-lite/lib/index.css'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import throttle from 'lodash/throttle'
|
||||
import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { Highlight } from '../../lib/networking/fragments/highlightFragment'
|
||||
import { Button } from '../elements/Button'
|
||||
import {
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
ModalRoot,
|
||||
} from '../elements/ModalPrimitives'
|
||||
import { CloseButton } from '../elements/CloseButton'
|
||||
import { StyledText } from '../elements/StyledText'
|
||||
|
||||
const mdParser = new MarkdownIt()
|
||||
|
||||
type NoteSectionProps = {
|
||||
placeHolder: string
|
||||
mode: 'edit' | 'preview'
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
setEditMode: (set: 'edit' | 'preview') => void
|
||||
|
||||
text: string | undefined
|
||||
saveText: (text: string, completed: (success: boolean) => void) => void
|
||||
}
|
||||
|
||||
export function HighlightNoteBox(props: NoteSectionProps): JSX.Element {
|
||||
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
||||
|
||||
const saveText = useCallback(
|
||||
(text, updateTime) => {
|
||||
props.saveText(text, (success) => {
|
||||
if (success) {
|
||||
setLastSaved(updateTime)
|
||||
}
|
||||
})
|
||||
},
|
||||
[props]
|
||||
)
|
||||
|
||||
return (
|
||||
<MarkdownNote
|
||||
placeHolder={props.placeHolder}
|
||||
mode={props.mode}
|
||||
sizeMode={props.sizeMode}
|
||||
setEditMode={props.setEditMode}
|
||||
text={props.text}
|
||||
saveText={saveText}
|
||||
lastSaved={lastSaved}
|
||||
fillBackground={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type HighlightViewNoteProps = {
|
||||
placeHolder: string
|
||||
mode: 'edit' | 'preview'
|
||||
|
||||
highlight: Highlight
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
setEditMode: (set: 'edit' | 'preview') => void
|
||||
|
||||
text: string | undefined
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
}
|
||||
|
||||
export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
||||
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
||||
|
||||
const saveText = useCallback(
|
||||
(text, updateTime) => {
|
||||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
annotation: text,
|
||||
highlightId: props.highlight?.id,
|
||||
})
|
||||
if (success) {
|
||||
setLastSaved(updateTime)
|
||||
props.highlight.annotation = text
|
||||
props.updateHighlight(props.highlight)
|
||||
}
|
||||
})()
|
||||
},
|
||||
[props]
|
||||
)
|
||||
|
||||
return (
|
||||
<MarkdownNote
|
||||
placeHolder={props.placeHolder}
|
||||
mode={props.mode}
|
||||
sizeMode={props.sizeMode}
|
||||
setEditMode={props.setEditMode}
|
||||
text={props.text}
|
||||
saveText={saveText}
|
||||
lastSaved={lastSaved}
|
||||
fillBackground={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type MarkdownNote = {
|
||||
placeHolder: string
|
||||
mode: 'edit' | 'preview'
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
setEditMode: (set: 'edit' | 'preview') => void
|
||||
|
||||
text: string | undefined
|
||||
fillBackground: boolean | undefined
|
||||
|
||||
lastSaved: Date | undefined
|
||||
saveText: (text: string, updateTime: Date) => void
|
||||
}
|
||||
|
||||
export function MarkdownNote(props: MarkdownNote): JSX.Element {
|
||||
const editorRef = useRef<MdEditor | null>(null)
|
||||
const [lastChanged, setLastChanged] = useState<Date | undefined>(undefined)
|
||||
const [errorSaving, setErrorSaving] = useState<string | undefined>(undefined)
|
||||
|
||||
const saveRef = useRef(props.saveText)
|
||||
|
||||
useEffect(() => {
|
||||
saveRef.current = props.saveText
|
||||
}, [props.lastSaved, lastChanged])
|
||||
|
||||
const debouncedSave = useMemo<
|
||||
(text: string, updateTime: Date) => void
|
||||
>(() => {
|
||||
const func = (text: string, updateTime: Date) => {
|
||||
saveRef.current?.(text, updateTime)
|
||||
}
|
||||
return throttle(func, 3000)
|
||||
}, [])
|
||||
|
||||
const handleEditorChange = useCallback(
|
||||
(
|
||||
data: { text: string; html: string },
|
||||
event?: ChangeEvent<HTMLTextAreaElement> | undefined
|
||||
) => {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const updateTime = new Date()
|
||||
setLastChanged(updateTime)
|
||||
debouncedSave(data.text, updateTime)
|
||||
},
|
||||
[props.lastSaved, lastChanged]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.mode == 'edit' ? (
|
||||
<VStack
|
||||
css={{
|
||||
width: '100%',
|
||||
mt: '15px',
|
||||
'.rc-md-editor': {
|
||||
borderRadius: '5px',
|
||||
},
|
||||
'.rc-md-navigation': {
|
||||
borderRadius: '5px',
|
||||
borderBottomLeftRadius: '0px',
|
||||
borderBottomRightRadius: '0px',
|
||||
},
|
||||
'.rc-md-editor .editor-container >.section': {
|
||||
borderRight: 'unset',
|
||||
},
|
||||
'.rc-md-editor .editor-container .sec-md .input': {
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
fontSize: '16px',
|
||||
},
|
||||
}}
|
||||
onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.code.toLowerCase() === 'escape') {
|
||||
props.setEditMode('preview')
|
||||
event.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MdEditor
|
||||
key="note-editor"
|
||||
ref={editorRef}
|
||||
autoFocus={true}
|
||||
defaultValue={props.text}
|
||||
placeholder={props.placeHolder}
|
||||
view={{ menu: true, md: true, html: false }}
|
||||
canView={{
|
||||
menu: props.mode == 'edit',
|
||||
md: true,
|
||||
html: true,
|
||||
both: false,
|
||||
fullScreen: false,
|
||||
hideMenu: false,
|
||||
}}
|
||||
plugins={[
|
||||
'header',
|
||||
'font-bold',
|
||||
'font-italic',
|
||||
'font-underline',
|
||||
'font-strikethrough',
|
||||
'list-unordered',
|
||||
'list-ordered',
|
||||
'block-quote',
|
||||
'link',
|
||||
'auto-resize',
|
||||
]}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: props.sizeMode == 'normal' ? '160px' : '320px',
|
||||
}}
|
||||
renderHTML={(text: string) => mdParser.render(text)}
|
||||
onChange={handleEditorChange}
|
||||
/>
|
||||
<HStack
|
||||
css={{
|
||||
minHeight: '15px',
|
||||
width: '100%',
|
||||
fontSize: '9px',
|
||||
mt: '1px',
|
||||
color: '$thTextSubtle',
|
||||
}}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
>
|
||||
{errorSaving && (
|
||||
<SpanBox
|
||||
css={{
|
||||
width: '100%',
|
||||
fontSize: '9px',
|
||||
mt: '1px',
|
||||
color: 'red',
|
||||
}}
|
||||
>
|
||||
{errorSaving}
|
||||
</SpanBox>
|
||||
)}
|
||||
{props.lastSaved !== undefined ? (
|
||||
<>
|
||||
{lastChanged === props.lastSaved
|
||||
? 'Saved'
|
||||
: `Last saved ${formattedShortTime(
|
||||
props.lastSaved.toISOString()
|
||||
)}`}
|
||||
</>
|
||||
) : null}
|
||||
{lastChanged !== props.lastSaved && (
|
||||
<SpanBox
|
||||
css={{
|
||||
fontSize: '9px',
|
||||
mt: '1px',
|
||||
color: 'green',
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
css={{
|
||||
textDecoration: 'underline',
|
||||
border: 'unset',
|
||||
background: 'unset',
|
||||
'&:hover': {
|
||||
border: 'unset',
|
||||
background: 'unset',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
const value = editorRef.current?.getMdValue()
|
||||
if (value) {
|
||||
props.saveText(value, new Date())
|
||||
}
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</SpanBox>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
) : (
|
||||
<>
|
||||
<SpanBox
|
||||
css={{
|
||||
p: '5px',
|
||||
width: '100%',
|
||||
fontSize: '15px',
|
||||
borderRadius: '2px',
|
||||
marginTop: props.fillBackground || !props.text ? '10px' : '0px',
|
||||
|
||||
paddingLeft:
|
||||
props.fillBackground && props.text
|
||||
? '10px'
|
||||
: !props.text
|
||||
? '5px'
|
||||
: '0px',
|
||||
paddingRight:
|
||||
props.fillBackground && props.text
|
||||
? '10px'
|
||||
: !props.text
|
||||
? '5px'
|
||||
: '0px',
|
||||
color: props.text ? '$thHighContrast' : '#898989',
|
||||
border: props.text ? 'unset' : '1px solid $thBorderColor',
|
||||
background:
|
||||
props.text && props.fillBackground ? '$thBackground5' : 'unset',
|
||||
'> *': {
|
||||
m: '0px',
|
||||
},
|
||||
}}
|
||||
onClick={() => props.setEditMode('edit')}
|
||||
>
|
||||
<ReactMarkdown children={props.text ?? props.placeHolder} />
|
||||
</SpanBox>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type MarkdownModalProps = {
|
||||
placeHolder: string
|
||||
mode: 'edit' | 'preview'
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
setEditMode: (set: 'edit' | 'preview') => void
|
||||
|
||||
text: string | undefined
|
||||
saveText: (text: string, completed: (success: boolean) => void) => void
|
||||
}
|
||||
|
||||
export function MarkdownModal(props: MarkdownModalProps): JSX.Element {
|
||||
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
||||
|
||||
const saveText = useCallback(
|
||||
(text, updateTime) => {
|
||||
props.saveText(text, (success) => {
|
||||
if (success) {
|
||||
setLastSaved(updateTime)
|
||||
}
|
||||
})
|
||||
},
|
||||
[props]
|
||||
)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
console.log('onOpenChange')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ModalRoot
|
||||
defaultOpen
|
||||
onOpenChange={handleClose}
|
||||
css={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
<ModalOverlay css={{ width: '100%', height: '100%' }} />
|
||||
<ModalContent
|
||||
css={{
|
||||
bg: '$grayBg',
|
||||
zIndex: '30',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxHeight: 'unset',
|
||||
maxWidth: 'unset',
|
||||
}}
|
||||
>
|
||||
<VStack>
|
||||
<HStack
|
||||
distribution="between"
|
||||
alignment="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
position: 'sticky',
|
||||
top: '0px',
|
||||
height: '50px',
|
||||
p: '20px',
|
||||
bg: '$grayBg',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<StyledText style="modalHeadline" css={{ color: '$thTextSubtle2' }}>
|
||||
Edit Note
|
||||
</StyledText>
|
||||
<HStack
|
||||
css={{
|
||||
ml: 'auto',
|
||||
cursor: 'pointer',
|
||||
gap: '15px',
|
||||
mr: '-5px',
|
||||
}}
|
||||
distribution="center"
|
||||
alignment="center"
|
||||
>
|
||||
{/* <Dropdown triggerElement={<MenuTrigger />}>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlights()
|
||||
}}
|
||||
title="Export Notebook"
|
||||
/>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
setShowConfirmDeleteNote(true)
|
||||
}}
|
||||
title="Delete Document Note"
|
||||
/>
|
||||
</Dropdown> */}
|
||||
<CloseButton close={handleClose} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<SpanBox css={{ padding: '20px', width: '100%', height: '100%' }}>
|
||||
<MarkdownNote
|
||||
placeHolder={props.placeHolder}
|
||||
mode={props.mode}
|
||||
sizeMode={props.sizeMode}
|
||||
setEditMode={props.setEditMode}
|
||||
text={props.text}
|
||||
saveText={saveText}
|
||||
lastSaved={lastSaved}
|
||||
fillBackground={false}
|
||||
/>
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,60 +1,134 @@
|
|||
import { Fragment, useMemo } from 'react'
|
||||
/* eslint-disable react/no-children-prop */
|
||||
import { BookOpen, PencilLine } from 'phosphor-react'
|
||||
import { useState } from 'react'
|
||||
import type { Highlight } from '../../lib/networking/fragments/highlightFragment'
|
||||
import { LabelChip } from '../elements/LabelChip'
|
||||
import { Box, VStack, Blockquote, SpanBox } from '../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../elements/StyledText'
|
||||
import {
|
||||
Box,
|
||||
VStack,
|
||||
Blockquote,
|
||||
SpanBox,
|
||||
HStack,
|
||||
} from '../elements/LayoutPrimitives'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { HighlightViewNote, MarkdownModal } from './HighlightNotes'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
type HighlightViewProps = {
|
||||
highlight: Highlight
|
||||
author?: string
|
||||
title?: string
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
}
|
||||
|
||||
const StyledQuote = styled(Blockquote, {
|
||||
margin: '0px 0px 0px 0px',
|
||||
fontSize: '18px',
|
||||
lineHeight: '27px',
|
||||
color: '$grayText',
|
||||
padding: '0px 16px',
|
||||
borderLeft: '2px solid $omnivoreCtaYellow',
|
||||
})
|
||||
|
||||
export function HighlightView(props: HighlightViewProps): JSX.Element {
|
||||
const lines = useMemo(
|
||||
() => props.highlight.quote.split('\n'),
|
||||
[props.highlight.quote]
|
||||
)
|
||||
const [noteMode, setNoteMode] = useState<'preview' | 'edit'>('preview')
|
||||
|
||||
return (
|
||||
<VStack css={{ width: '100%', boxSizing: 'border-box' }}>
|
||||
<StyledQuote
|
||||
onClick={() => {
|
||||
if (props.scrollToHighlight) {
|
||||
props.scrollToHighlight(props.highlight.id)
|
||||
}
|
||||
<HStack
|
||||
css={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: 'stretch',
|
||||
}}
|
||||
>
|
||||
<VStack css={{ minHeight: '100%', width: '10px' }}>
|
||||
<Box
|
||||
css={{
|
||||
mt: '5px',
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
background: '#FFD234',
|
||||
borderRadius: '7px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
css={{
|
||||
width: '2px',
|
||||
flexGrow: '1',
|
||||
background: '#FFD234',
|
||||
marginLeft: '4px',
|
||||
flex: '1',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
/>
|
||||
</VStack>
|
||||
<VStack
|
||||
css={{
|
||||
width: '100%',
|
||||
padding: '0px',
|
||||
paddingLeft: '15px',
|
||||
}}
|
||||
>
|
||||
<SpanBox css={{ p: '1px', borderRadius: '2px' }}>
|
||||
{lines.map((line: string, index: number) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
{index !== lines.length - 1 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
<StyledQuote>
|
||||
<SpanBox
|
||||
css={{
|
||||
'> *': {
|
||||
m: '0px',
|
||||
},
|
||||
fontSize: '15px',
|
||||
lineHeight: 1.5,
|
||||
color: '$grayText',
|
||||
img: {
|
||||
display: 'block',
|
||||
margin: '0.5em auto !important',
|
||||
maxWidth: '100% !important',
|
||||
height: 'auto',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ReactMarkdown children={props.highlight.quote ?? ''} />
|
||||
</SpanBox>
|
||||
</StyledQuote>
|
||||
<Box css={{ display: 'block', pt: '5px' }}>
|
||||
{props.highlight.labels?.map(({ name, color }, index) => (
|
||||
<LabelChip key={index} text={name || ''} color={color} />
|
||||
))}
|
||||
</SpanBox>
|
||||
</StyledQuote>
|
||||
<Box css={{ display: 'block', pt: '16px' }}>
|
||||
{props.highlight.labels?.map(({ name, color }, index) => (
|
||||
<LabelChip key={index} text={name || ''} color={color} />
|
||||
))}
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
<HStack
|
||||
css={{ width: '100%', height: '100%', pt: '15px' }}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
>
|
||||
<HighlightViewNote
|
||||
text={props.highlight.annotation}
|
||||
placeHolder="Add notes to this highlight..."
|
||||
highlight={props.highlight}
|
||||
sizeMode={'normal'}
|
||||
mode={noteMode}
|
||||
setEditMode={setNoteMode}
|
||||
updateHighlight={props.updateHighlight}
|
||||
/>
|
||||
<SpanBox
|
||||
css={{
|
||||
lineHeight: '1',
|
||||
marginLeft: '20px',
|
||||
marginTop: '15px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '1000px',
|
||||
'&:hover': {
|
||||
background: '#EBEBEB',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
setNoteMode(noteMode == 'preview' ? 'edit' : 'preview')
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{noteMode === 'edit' ? (
|
||||
<BookOpen size={15} color="#898989" />
|
||||
) : (
|
||||
<PencilLine size={15} color="#898989" />
|
||||
)}
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, VStack, HStack } from '../../elements/LayoutPrimitives'
|
||||
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { CaretDown, CaretUp } from 'phosphor-react'
|
||||
import { MetaStyle, timeAgo, TitleStyle } from './LibraryCardStyles'
|
||||
|
|
@ -7,9 +7,9 @@ import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery
|
|||
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { HighlightItem } from '../../templates/homeFeed/HighlightItem'
|
||||
import { getHighlightLocation } from '../../templates/article/NotebookModal'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { HighlightView } from '../HighlightView'
|
||||
|
||||
export const GridSeparator = styled(Box, {
|
||||
height: '1px',
|
||||
|
|
@ -46,21 +46,23 @@ export function LibraryHighlightGridCard(
|
|||
return []
|
||||
}
|
||||
|
||||
return props.item.highlights.sort((a: Highlight, b: Highlight) => {
|
||||
if (a.highlightPositionPercent && b.highlightPositionPercent) {
|
||||
return sorted(a.highlightPositionPercent, b.highlightPositionPercent)
|
||||
}
|
||||
// We do this in a try/catch because it might be an invalid diff
|
||||
// With PDF it will definitely be an invalid diff.
|
||||
try {
|
||||
const aPos = getHighlightLocation(a.patch)
|
||||
const bPos = getHighlightLocation(b.patch)
|
||||
if (aPos && bPos) {
|
||||
return sorted(aPos, bPos)
|
||||
return props.item.highlights
|
||||
.filter((h) => h.type === 'HIGHLIGHT')
|
||||
.sort((a: Highlight, b: Highlight) => {
|
||||
if (a.highlightPositionPercent && b.highlightPositionPercent) {
|
||||
return sorted(a.highlightPositionPercent, b.highlightPositionPercent)
|
||||
}
|
||||
} catch {}
|
||||
return a.createdAt.localeCompare(b.createdAt)
|
||||
})
|
||||
// We do this in a try/catch because it might be an invalid diff
|
||||
// With PDF it will definitely be an invalid diff.
|
||||
try {
|
||||
const aPos = getHighlightLocation(a.patch)
|
||||
const bPos = getHighlightLocation(b.patch)
|
||||
if (aPos && bPos) {
|
||||
return sorted(aPos, bPos)
|
||||
}
|
||||
} catch {}
|
||||
return a.createdAt.localeCompare(b.createdAt)
|
||||
})
|
||||
}, [props.item.highlights])
|
||||
|
||||
return (
|
||||
|
|
@ -121,17 +123,20 @@ export function LibraryHighlightGridCard(
|
|||
<>
|
||||
<GridSeparator css={{ width: '100%' }} />
|
||||
<VStack
|
||||
css={{ height: '100%', width: '100%' }}
|
||||
css={{ height: '100%', width: '100%', mt: '20px' }}
|
||||
distribution="start"
|
||||
>
|
||||
{sortedHighlights.map((highlight) => (
|
||||
<HighlightItem
|
||||
key={highlight.id}
|
||||
viewer={props.viewer}
|
||||
item={props.item}
|
||||
highlight={highlight}
|
||||
deleteHighlight={props.deleteHighlight}
|
||||
/>
|
||||
<SpanBox key={`hv-${highlight.id}`}>
|
||||
<HighlightView
|
||||
key={highlight.id}
|
||||
highlight={highlight}
|
||||
updateHighlight={(highlight) => {
|
||||
console.log('updated highlight: ', highlight)
|
||||
}}
|
||||
/>
|
||||
<SpanBox css={{ height: '35px' }} />
|
||||
</SpanBox>
|
||||
))}
|
||||
</VStack>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ 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 { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
type ArticleContainerProps = {
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
labels: Label[]
|
||||
articleMutations: ArticleMutations
|
||||
isAppleAppEmbed: boolean
|
||||
highlightBarDisabled: boolean
|
||||
highlightsBaseURL: string
|
||||
margin?: number
|
||||
fontSize?: number
|
||||
fontFamily?: string
|
||||
|
|
@ -107,15 +108,12 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
|
||||
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
|
||||
// iOS app embed can overide the original margin and line height
|
||||
const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] = useState<
|
||||
number | null
|
||||
>(null)
|
||||
const [lineHeightOverride, setLineHeightOverride] = useState<number | null>(
|
||||
null
|
||||
)
|
||||
const [fontFamilyOverride, setFontFamilyOverride] = useState<string | null>(
|
||||
null
|
||||
)
|
||||
const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] =
|
||||
useState<number | null>(null)
|
||||
const [lineHeightOverride, setLineHeightOverride] =
|
||||
useState<number | null>(null)
|
||||
const [fontFamilyOverride, setFontFamilyOverride] =
|
||||
useState<string | null>(null)
|
||||
const [highContrastText, setHighContrastText] = useState(
|
||||
props.highContrastText ?? false
|
||||
)
|
||||
|
|
@ -388,13 +386,14 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
<Box css={{ height: '100px' }} />
|
||||
</Box>
|
||||
<HighlightsLayer
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
scrollToHighlight={highlightHref}
|
||||
highlights={props.article.highlights}
|
||||
articleTitle={props.article.title}
|
||||
articleAuthor={props.article.author ?? ''}
|
||||
articleId={props.article.id}
|
||||
isAppleAppEmbed={props.isAppleAppEmbed}
|
||||
highlightsBaseURL={props.highlightsBaseURL}
|
||||
highlightBarDisabled={props.highlightBarDisabled}
|
||||
showHighlightsModal={props.showHighlightsModal}
|
||||
setShowHighlightsModal={props.setShowHighlightsModal}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
|
||||
type PageCoordinates = {
|
||||
pageX: number
|
||||
pageY: number
|
||||
}
|
||||
|
||||
type HighlightHoverCardProps = {
|
||||
highlight: Highlight
|
||||
anchorCoordinates: PageCoordinates
|
||||
}
|
||||
|
||||
export function HighlightHoverCard(
|
||||
props: HighlightHoverCardProps
|
||||
): JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
width: '100%',
|
||||
maxWidth: '330px',
|
||||
// height: '48px',
|
||||
position: 'absolute',
|
||||
background: '$grayBg',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid $grayBorder',
|
||||
boxShadow: theme.shadows.cardBoxShadow.toString(),
|
||||
left: props.anchorCoordinates.pageX,
|
||||
top: props.anchorCoordinates.pageY,
|
||||
}}
|
||||
>
|
||||
<HighlightView highlight={props.highlight} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import {
|
||||
ModalRoot,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
} from './../../elements/ModalPrimitives'
|
||||
import { Box, HStack } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { StyledTextArea } from '../../elements/StyledTextArea'
|
||||
|
||||
type HighlightPostToFeedModalProps = {
|
||||
highlight: Highlight
|
||||
author: string
|
||||
title: string
|
||||
onCommit: (highlight: Highlight, comment: string) => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function HighlightPostToFeedModal(
|
||||
props: HighlightPostToFeedModalProps
|
||||
): JSX.Element {
|
||||
const [comment, setComment] = useState('')
|
||||
|
||||
const handleCommentChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
||||
setComment(event.target.value)
|
||||
},
|
||||
[setComment]
|
||||
)
|
||||
|
||||
const postHighlight = useCallback(async () => {
|
||||
props.onCommit(props.highlight, comment)
|
||||
props.onOpenChange(false)
|
||||
}, [comment, props])
|
||||
|
||||
return (
|
||||
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
onPointerDownOutside={(event) => {
|
||||
event.preventDefault()
|
||||
}}
|
||||
css={{ overflow: 'auto' }}
|
||||
>
|
||||
<Box
|
||||
css={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto 1fr',
|
||||
px: '$2',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style="ctaSecondary"
|
||||
onClick={() => {
|
||||
props.onOpenChange(false)
|
||||
}}
|
||||
css={{ justifySelf: 'start' }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<HStack alignment="center">
|
||||
<StyledText>Post Highlight</StyledText>
|
||||
</HStack>
|
||||
<Button
|
||||
style="ctaSecondary"
|
||||
onClick={postHighlight}
|
||||
css={{ justifySelf: 'end' }}
|
||||
>
|
||||
Post
|
||||
</Button>
|
||||
</Box>
|
||||
<HighlightView {...props} />
|
||||
<StyledTextArea
|
||||
css={{
|
||||
mt: '$2',
|
||||
width: '95%',
|
||||
p: '$1',
|
||||
minHeight: '$6',
|
||||
}}
|
||||
autoFocus
|
||||
placeholder={'Leave comment (optional)'}
|
||||
value={comment}
|
||||
onChange={handleCommentChange}
|
||||
maxLength={1000}
|
||||
/>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { useState } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import {
|
||||
LibraryItem,
|
||||
ReadableItem,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
import { HighlightsMenu } from '../homeFeed/HighlightItem'
|
||||
|
||||
type HighlightViewItemProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlight: Highlight
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
deleteHighlightAction: () => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
|
||||
setSetLabelsTarget: (highlight: Highlight) => void
|
||||
setShowConfirmDeleteHighlightId: (id: string | undefined) => void
|
||||
}
|
||||
|
||||
export function HighlightViewItem(props: HighlightViewItemProps): JSX.Element {
|
||||
const [hover, setHover] = useState(false)
|
||||
|
||||
return (
|
||||
<HStack
|
||||
css={{ width: '100%', py: '20px' }}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<VStack css={{ width: '100%' }}>
|
||||
<HighlightView
|
||||
highlight={props.highlight}
|
||||
updateHighlight={props.updateHighlight}
|
||||
/>
|
||||
<SpanBox css={{ mb: '15px' }} />
|
||||
</VStack>
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
width: '20px',
|
||||
visibility: hover ? 'unset' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HighlightsMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
highlight={props.highlight}
|
||||
viewInReader={props.viewInReader}
|
||||
setLabelsTarget={props.setSetLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={
|
||||
props.setShowConfirmDeleteHighlightId
|
||||
}
|
||||
/>
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
|
@ -26,17 +26,25 @@ import { isTouchScreenDevice } from '../../../lib/deviceType'
|
|||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { useRouter } from 'next/router'
|
||||
import { MarkdownModal } from '../../patterns/HighlightNotes'
|
||||
|
||||
type HighlightsLayerProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
|
||||
articleId: string
|
||||
articleTitle: string
|
||||
articleAuthor: string
|
||||
isAppleAppEmbed: boolean
|
||||
highlightBarDisabled: boolean
|
||||
showHighlightsModal: boolean
|
||||
highlightsBaseURL: string
|
||||
scrollToHighlight: MutableRefObject<string | null>
|
||||
|
||||
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
|
||||
articleMutations: ArticleMutations
|
||||
}
|
||||
|
|
@ -59,6 +67,7 @@ interface SpeakingSectionEvent extends Event {
|
|||
}
|
||||
|
||||
export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [highlights, setHighlights] = useState(props.highlights)
|
||||
const [highlightModalAction, setHighlightModalAction] =
|
||||
useState<HighlightActionProps>({ highlightModalAction: 'none' })
|
||||
|
|
@ -68,15 +77,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
>([])
|
||||
const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 })
|
||||
|
||||
const [focusedHighlight, setFocusedHighlight] = useState<
|
||||
Highlight | undefined
|
||||
>(undefined)
|
||||
const [focusedHighlight, setFocusedHighlight] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
|
||||
const [selectionData, setSelectionData] = useSelection(highlightLocations)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
|
||||
const canShareNative = useCanShareNative()
|
||||
|
||||
|
|
@ -121,14 +128,16 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
// Load the highlights
|
||||
useEffect(() => {
|
||||
const res: HighlightLocation[] = []
|
||||
highlights.forEach((highlight) => {
|
||||
try {
|
||||
const offset = makeHighlightStartEndOffset(highlight)
|
||||
res.push(offset)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
highlights
|
||||
.filter((h) => h.type == 'HIGHLIGHT')
|
||||
.forEach((highlight) => {
|
||||
try {
|
||||
const offset = makeHighlightStartEndOffset(highlight)
|
||||
res.push(offset)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
setHighlightLocations(res)
|
||||
|
||||
// If we were given an initial highlight to scroll to we do
|
||||
|
|
@ -139,7 +148,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
`[omnivore-highlight-id="${props.scrollToHighlight.current}"]`
|
||||
)
|
||||
if (anchorElement) {
|
||||
anchorElement.scrollIntoView({ behavior: 'auto' })
|
||||
anchorElement.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'auto',
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [highlights, setHighlightLocations, props.scrollToHighlight])
|
||||
|
|
@ -179,23 +191,20 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
[highlights, highlightLocations]
|
||||
)
|
||||
|
||||
const handleNativeShare = useCallback(
|
||||
(highlightID: string) => {
|
||||
navigator
|
||||
?.share({
|
||||
title: props.articleTitle,
|
||||
url: `${props.highlightsBaseURL}/${highlightID}`,
|
||||
})
|
||||
.then(() => {
|
||||
setFocusedHighlight(undefined)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
setFocusedHighlight(undefined)
|
||||
})
|
||||
},
|
||||
[props.articleTitle, props.highlightsBaseURL]
|
||||
)
|
||||
// const handleNativeShare = useCallback((highlightID: string) => {
|
||||
// // navigator
|
||||
// // ?.share({
|
||||
// // title: props.articleTitle,
|
||||
// // url: `${props.highlightsBaseURL}/${highlightID}`,
|
||||
// // })
|
||||
// // .then(() => {
|
||||
// // setFocusedHighlight(undefined)
|
||||
// // })
|
||||
// // .catch((error) => {
|
||||
// // console.log(error)
|
||||
// // setFocusedHighlight(undefined)
|
||||
// // })
|
||||
// }, [])
|
||||
|
||||
const openNoteModal = useCallback(
|
||||
(inputs: HighlightActionProps) => {
|
||||
|
|
@ -280,7 +289,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
}
|
||||
},
|
||||
[
|
||||
handleNativeShare,
|
||||
highlights,
|
||||
openNoteModal,
|
||||
props.articleId,
|
||||
|
|
@ -356,6 +364,24 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
[highlights, highlightLocations, openNoteModal]
|
||||
)
|
||||
|
||||
const handleCloseNotebook = useCallback(
|
||||
(updatedHighlights: Highlight[], deletedHighlights: Highlight[]) => {
|
||||
props.setShowHighlightsModal(false)
|
||||
|
||||
setHighlights(updatedHighlights)
|
||||
|
||||
removeHighlights(
|
||||
deletedHighlights.map((h) => h.id),
|
||||
highlightLocations
|
||||
)
|
||||
|
||||
updatedHighlights.forEach((h) => {
|
||||
updateHighlightsCallback(h)
|
||||
})
|
||||
},
|
||||
[highlights, highlightLocations]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
|
|
@ -388,37 +414,37 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
})
|
||||
}
|
||||
break
|
||||
case 'share':
|
||||
if (props.isAppleAppEmbed) {
|
||||
window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
actionID: 'share',
|
||||
highlightID: focusedHighlight?.id,
|
||||
})
|
||||
}
|
||||
// case 'share':
|
||||
// if (props.isAppleAppEmbed) {
|
||||
// window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
// actionID: 'share',
|
||||
// highlightID: focusedHighlight?.id,
|
||||
// })
|
||||
// }
|
||||
|
||||
window?.AndroidWebKitMessenger?.handleIdentifiableMessage(
|
||||
'shareHighlight',
|
||||
JSON.stringify({
|
||||
highlightID: focusedHighlight?.id,
|
||||
})
|
||||
)
|
||||
// window?.AndroidWebKitMessenger?.handleIdentifiableMessage(
|
||||
// 'shareHighlight',
|
||||
// JSON.stringify({
|
||||
// highlightID: focusedHighlight?.id,
|
||||
// })
|
||||
// )
|
||||
|
||||
if (focusedHighlight) {
|
||||
if (canShareNative) {
|
||||
handleNativeShare(focusedHighlight.shortId)
|
||||
} else {
|
||||
setHighlightModalAction({
|
||||
highlight: focusedHighlight,
|
||||
highlightModalAction: 'share',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await createHighlightCallback('share')
|
||||
}
|
||||
break
|
||||
case 'unshare':
|
||||
console.log('unshare')
|
||||
break // TODO: implement -- need to show confirmation dialog
|
||||
// if (focusedHighlight) {
|
||||
// if (canShareNative) {
|
||||
// handleNativeShare(focusedHighlight.shortId)
|
||||
// } else {
|
||||
// setHighlightModalAction({
|
||||
// highlight: focusedHighlight,
|
||||
// highlightModalAction: 'share',
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// await createHighlightCallback('share')
|
||||
// }
|
||||
// break
|
||||
// case 'unshare':
|
||||
// console.log('unshare')
|
||||
// break // TODO: implement -- need to show confirmation dialog
|
||||
case 'setHighlightLabels':
|
||||
if (props.isAppleAppEmbed) {
|
||||
window?.webkit?.messageHandlers.highlightAction?.postMessage({
|
||||
|
|
@ -434,7 +460,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
[
|
||||
createHighlightCallback,
|
||||
focusedHighlight,
|
||||
handleNativeShare,
|
||||
openNoteModal,
|
||||
props.highlightBarDisabled,
|
||||
props.isAppleAppEmbed,
|
||||
|
|
@ -498,7 +523,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
}
|
||||
|
||||
const copy = async () => {
|
||||
if (focusedHighlight) {
|
||||
if (focusedHighlight && focusedHighlight.quote) {
|
||||
if (window.AndroidWebKitMessenger) {
|
||||
window.AndroidWebKitMessenger.handleIdentifiableMessage(
|
||||
'writeToClipboard',
|
||||
|
|
@ -647,12 +672,28 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
if (props.showHighlightsModal) {
|
||||
return (
|
||||
<NotebookModal
|
||||
viewer={props.viewer}
|
||||
item={props.item}
|
||||
highlights={highlights}
|
||||
onOpenChange={() => props.setShowHighlightsModal(false)}
|
||||
deleteHighlightAction={(highlightId: string) => {
|
||||
removeHighlightCallback(highlightId)
|
||||
onClose={handleCloseNotebook}
|
||||
viewHighlightInReader={(highlightId) => {
|
||||
// The timeout here is a bit of a hack to work around rerendering
|
||||
setTimeout(() => {
|
||||
const target = document.querySelector(
|
||||
`[omnivore-highlight-id="${highlightId}"]`
|
||||
)
|
||||
target?.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'auto',
|
||||
})
|
||||
}, 1)
|
||||
history.replaceState(
|
||||
undefined,
|
||||
window.location.href,
|
||||
`#${highlightId}`
|
||||
)
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
updateHighlight={updateHighlightsCallback}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
482
packages/web/components/templates/article/Notebook.tsx
Normal file
482
packages/web/components/templates/article/Notebook.tsx
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
import { Box, HStack, VStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { BookOpen, PencilLine, X } from 'phosphor-react'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { diff_match_patch } from 'diff-match-patch'
|
||||
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
|
||||
import 'react-markdown-editor-lite/lib/index.css'
|
||||
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
|
||||
import { HighlightNoteBox, MarkdownNote } from '../../patterns/HighlightNotes'
|
||||
import { HighlightViewItem } from './HighlightViewItem'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { TrashIcon } from '../../elements/images/TrashIcon'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import {
|
||||
LibraryItem,
|
||||
ReadableItem,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
|
||||
type NotebookProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
|
||||
sizeMode: 'normal' | 'maximized'
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
onAnnotationsChanged?: (
|
||||
highlights: Highlight[],
|
||||
deletedAnnotations: Highlight[]
|
||||
) => void
|
||||
}
|
||||
|
||||
export const getHighlightLocation = (patch: string): number | undefined => {
|
||||
const dmp = new diff_match_patch()
|
||||
const patches = dmp.patch_fromText(patch)
|
||||
return patches[0].start1 || undefined
|
||||
}
|
||||
|
||||
type AnnotationInfo = {
|
||||
loaded: boolean
|
||||
|
||||
note: Highlight | undefined
|
||||
noteId: string
|
||||
|
||||
allAnnotations: Highlight[]
|
||||
deletedAnnotations: Highlight[]
|
||||
}
|
||||
|
||||
export function Notebook(props: NotebookProps): JSX.Element {
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
|
||||
useState<undefined | string>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
|
||||
const [notesEditMode, setNotesEditMode] =
|
||||
useState<'edit' | 'preview'>('preview')
|
||||
const [, updateState] = useState({})
|
||||
|
||||
const annotationsReducer = (
|
||||
state: AnnotationInfo,
|
||||
action: {
|
||||
type: string
|
||||
allHighlights?: Highlight[]
|
||||
note?: Highlight | undefined
|
||||
|
||||
updateHighlight?: Highlight | undefined
|
||||
deleteHighlightId?: string | undefined
|
||||
}
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'RESET': {
|
||||
const note = action.allHighlights?.find((h) => h.type == 'NOTE')
|
||||
return {
|
||||
...state,
|
||||
loaded: true,
|
||||
note: note,
|
||||
noteId: note?.id ?? state.noteId,
|
||||
allAnnotations: [...(action.allHighlights ?? [])],
|
||||
}
|
||||
}
|
||||
case 'CREATE_NOTE': {
|
||||
if (!action.note) {
|
||||
throw new Error('No note on CREATE_NOTE action')
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
note: action.note,
|
||||
noteId: action.note.id,
|
||||
allAnnotations: [...state.allAnnotations, action.note],
|
||||
}
|
||||
}
|
||||
case 'DELETE_NOTE': {
|
||||
// If there is no note to delete, just make sure we have cleared out the note
|
||||
const noteId = action.note?.id
|
||||
if (!action.note?.id) {
|
||||
return {
|
||||
...state,
|
||||
node: undefined,
|
||||
noteId: uuidv4(),
|
||||
}
|
||||
}
|
||||
const idx = state.allAnnotations.findIndex((h) => h.id === noteId)
|
||||
return {
|
||||
...state,
|
||||
note: undefined,
|
||||
noteId: uuidv4(),
|
||||
allAnnotations: state.allAnnotations.splice(idx, 1),
|
||||
}
|
||||
}
|
||||
case 'DELETE_HIGHLIGHT': {
|
||||
const highlightId = action.deleteHighlightId
|
||||
console.log(' DELETE_HIGHLIGHT: ', highlightId)
|
||||
|
||||
if (!highlightId) {
|
||||
throw new Error('No highlightId for delete action.')
|
||||
}
|
||||
const idx = state.allAnnotations.findIndex((h) => h.id === highlightId)
|
||||
if (idx < 0) {
|
||||
return { ...state }
|
||||
}
|
||||
const deleted = state.deletedAnnotations
|
||||
deleted.push(state.allAnnotations[idx])
|
||||
|
||||
return {
|
||||
...state,
|
||||
deletedAnnotations: deleted,
|
||||
allAnnotations: state.allAnnotations.splice(idx, 1),
|
||||
}
|
||||
}
|
||||
case 'UPDATE_HIGHLIGHT': {
|
||||
const highlight = action.updateHighlight
|
||||
if (!highlight) {
|
||||
throw new Error('No highlightId for delete action.')
|
||||
}
|
||||
const idx = state.allAnnotations.findIndex((h) => h.id === highlight.id)
|
||||
if (idx !== -1) {
|
||||
state.allAnnotations[idx] = highlight
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const [annotations, dispatchAnnotations] = useReducer(annotationsReducer, {
|
||||
loaded: false,
|
||||
note: undefined,
|
||||
noteId: uuidv4(),
|
||||
allAnnotations: [],
|
||||
deletedAnnotations: [],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
dispatchAnnotations({
|
||||
type: 'RESET',
|
||||
allHighlights: props.highlights,
|
||||
})
|
||||
}, [props.highlights])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.onAnnotationsChanged) {
|
||||
props.onAnnotationsChanged(
|
||||
annotations.allAnnotations,
|
||||
annotations.deletedAnnotations
|
||||
)
|
||||
}
|
||||
}, [annotations])
|
||||
|
||||
const deleteDocumentNote = useCallback(() => {
|
||||
const note = annotations.note
|
||||
if (!note) {
|
||||
showErrorToast('No note found')
|
||||
return
|
||||
}
|
||||
;(async () => {
|
||||
try {
|
||||
const result = await deleteHighlightMutation(note.id)
|
||||
if (!result) {
|
||||
throw new Error()
|
||||
}
|
||||
showSuccessToast('Note deleted')
|
||||
dispatchAnnotations({
|
||||
note,
|
||||
type: 'DELETE_NOTE',
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('error deleting note', err)
|
||||
showErrorToast('Error deleting note')
|
||||
}
|
||||
})()
|
||||
}, [annotations])
|
||||
|
||||
const sortedHighlights = useMemo(() => {
|
||||
const sorted = (a: number, b: number) => {
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return annotations.allAnnotations
|
||||
.filter((h) => h.type === 'HIGHLIGHT')
|
||||
.sort((a: Highlight, b: Highlight) => {
|
||||
if (a.highlightPositionPercent && b.highlightPositionPercent) {
|
||||
return sorted(a.highlightPositionPercent, b.highlightPositionPercent)
|
||||
}
|
||||
// We do this in a try/catch because it might be an invalid diff
|
||||
// With PDF it will definitely be an invalid diff.
|
||||
try {
|
||||
const aPos = getHighlightLocation(a.patch)
|
||||
const bPos = getHighlightLocation(b.patch)
|
||||
if (aPos && bPos) {
|
||||
return sorted(aPos, bPos)
|
||||
}
|
||||
} catch {}
|
||||
return a.createdAt.localeCompare(b.createdAt)
|
||||
})
|
||||
}, [annotations])
|
||||
|
||||
const handleSaveNoteText = useCallback(
|
||||
(text, cb: (success: boolean) => void) => {
|
||||
if (!annotations.loaded) {
|
||||
// We haven't loaded the user's annotations yet, so we can't
|
||||
// find or create their highlight note.
|
||||
return
|
||||
}
|
||||
|
||||
if (!annotations.note) {
|
||||
const noteId = annotations.noteId
|
||||
;(async () => {
|
||||
const success = await createHighlightMutation({
|
||||
id: noteId,
|
||||
shortId: nanoid(8),
|
||||
type: 'NOTE',
|
||||
articleId: props.item.id,
|
||||
annotation: text,
|
||||
})
|
||||
console.log('success creating annotation note: ', success)
|
||||
if (success) {
|
||||
dispatchAnnotations({
|
||||
type: 'CREATE_NOTE',
|
||||
note: success,
|
||||
})
|
||||
}
|
||||
cb(!!success)
|
||||
})()
|
||||
return
|
||||
}
|
||||
|
||||
if (annotations.note) {
|
||||
const note = annotations.note
|
||||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
highlightId: note.id,
|
||||
annotation: text,
|
||||
})
|
||||
console.log('success updating annotation note: ', success)
|
||||
if (success) {
|
||||
note.annotation = text
|
||||
dispatchAnnotations({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note,
|
||||
})
|
||||
}
|
||||
cb(!!success)
|
||||
})()
|
||||
return
|
||||
}
|
||||
},
|
||||
[annotations, props.item]
|
||||
)
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{ height: '100%', width: '100%', p: '20px' }}
|
||||
>
|
||||
<TitledSection
|
||||
title="ARTICLE NOTES"
|
||||
editMode={notesEditMode == 'edit'}
|
||||
setEditMode={(edit) => setNotesEditMode(edit ? 'edit' : 'preview')}
|
||||
/>
|
||||
<HighlightNoteBox
|
||||
mode={notesEditMode}
|
||||
sizeMode={props.sizeMode}
|
||||
setEditMode={setNotesEditMode}
|
||||
text={annotations.note?.annotation}
|
||||
placeHolder="Add notes to this document..."
|
||||
saveText={handleSaveNoteText}
|
||||
/>
|
||||
<SpanBox css={{ mt: '10px', mb: '25px' }} />
|
||||
<Box css={{ width: '100%' }}>
|
||||
<TitledSection title="HIGHLIGHTS" />
|
||||
|
||||
{sortedHighlights.map((highlight) => (
|
||||
<HighlightViewItem
|
||||
key={highlight.id}
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
highlight={highlight}
|
||||
viewInReader={props.viewInReader}
|
||||
setSetLabelsTarget={setLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
|
||||
deleteHighlightAction={() => {
|
||||
dispatchAnnotations({
|
||||
type: 'DELETE_HIGHLIGHT',
|
||||
deleteHighlightId: highlight.id,
|
||||
})
|
||||
}}
|
||||
updateHighlight={() => {
|
||||
dispatchAnnotations({
|
||||
type: 'UPDATE_HIGHLIGHT',
|
||||
updateHighlight: highlight,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{sortedHighlights.length === 0 && (
|
||||
<Box
|
||||
css={{
|
||||
mt: '15px',
|
||||
width: '100%',
|
||||
fontSize: '9px',
|
||||
color: '$thTextSubtle',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mb: '100px',
|
||||
}}
|
||||
>
|
||||
You have not added any highlights to this document.
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
css={{
|
||||
'@mdDown': {
|
||||
height: '320px',
|
||||
width: '100%',
|
||||
background: 'transparent',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{showConfirmDeleteHighlightId && (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={() => {
|
||||
;(async () => {
|
||||
const success = await deleteHighlightMutation(
|
||||
showConfirmDeleteHighlightId
|
||||
)
|
||||
console.log(' ConfirmationModal::DeleteHighlight', success)
|
||||
if (success) {
|
||||
dispatchAnnotations({
|
||||
type: 'DELETE_HIGHLIGHT',
|
||||
deleteHighlightId: showConfirmDeleteHighlightId,
|
||||
})
|
||||
showSuccessToast('Highlight deleted.')
|
||||
} else {
|
||||
showErrorToast('Error deleting highlight')
|
||||
}
|
||||
})()
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
icon={
|
||||
<TrashIcon
|
||||
size={40}
|
||||
strokeColor={theme.colors.grayTextContrast.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{labelsTarget && (
|
||||
<SetLabelsModal
|
||||
provider={labelsTarget}
|
||||
onOpenChange={function (open: boolean): void {
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
onLabelsUpdated={function (labels: Label[]): void {
|
||||
updateState({})
|
||||
}}
|
||||
save={function (labels: Label[]): Promise<Label[] | undefined> {
|
||||
const result = setLabelsForHighlight(
|
||||
labelsTarget.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
return result
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDeleteNote && (
|
||||
<ConfirmationModal
|
||||
message="Are you sure you want to delete the note from this document?"
|
||||
acceptButtonLabel="Delete"
|
||||
onAccept={() => {
|
||||
deleteDocumentNote()
|
||||
setShowConfirmDeleteNote(false)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteNote(false)}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
type TitledSectionProps = {
|
||||
title: string
|
||||
editMode?: boolean
|
||||
setEditMode?: (set: boolean) => void
|
||||
}
|
||||
|
||||
function TitledSection(props: TitledSectionProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
css={{ width: '100%', borderBottom: '1px solid $thBorderColor' }}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
>
|
||||
<StyledText
|
||||
css={{
|
||||
fontFamily: '$display',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '700',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
color: '#898989',
|
||||
marginBottom: '1px',
|
||||
}}
|
||||
>
|
||||
{props.title}
|
||||
</StyledText>
|
||||
{props.setEditMode && (
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
justifyContent: 'end',
|
||||
lineHeight: '1',
|
||||
alignSelf: 'end',
|
||||
padding: '2px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '1000px',
|
||||
'&:hover': {
|
||||
background: '#EBEBEB',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (props.setEditMode) {
|
||||
props.setEditMode(!props.editMode)
|
||||
}
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.editMode ? (
|
||||
<BookOpen size={15} color="#898989" />
|
||||
) : (
|
||||
<PencilLine size={15} color="#898989" />
|
||||
)}
|
||||
</SpanBox>
|
||||
)}
|
||||
</HStack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,37 +2,33 @@ import {
|
|||
ModalRoot,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalTitleBar,
|
||||
} from '../../elements/ModalPrimitives'
|
||||
import { Box, HStack, VStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { TrashIcon } from '../../elements/images/TrashIcon'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { StyledTextArea } from '../../elements/StyledTextArea'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowsIn, ArrowsOut, X } from 'phosphor-react'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
import { SetLabelsModal } from './SetLabelsModal'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { diff_match_patch } from 'diff-match-patch'
|
||||
import { HighlightNoteTextEditArea } from '../../elements/HighlightNoteTextEditArea'
|
||||
import { CloseButton } from '../../elements/CloseButton'
|
||||
import { MenuTrigger } from '../../elements/MenuTrigger'
|
||||
import { highlightsAsMarkdown, HighlightsMenu } from '../homeFeed/HighlightItem'
|
||||
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
|
||||
import 'react-markdown-editor-lite/lib/index.css'
|
||||
import { Notebook } from './Notebook'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { MarkdownNote } from '../../patterns/HighlightNotes'
|
||||
|
||||
type NotebookModalProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlights: Highlight[]
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
deleteHighlightAction?: (highlightId: string) => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
|
||||
viewHighlightInReader: (arg: string) => void
|
||||
onClose: (highlights: Highlight[], deletedAnnotations: Highlight[]) => void
|
||||
}
|
||||
|
||||
export const getHighlightLocation = (patch: string): number | undefined => {
|
||||
|
|
@ -42,252 +38,179 @@ export const getHighlightLocation = (patch: string): number | undefined => {
|
|||
}
|
||||
|
||||
export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
|
||||
useState<undefined | string>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal')
|
||||
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
|
||||
const [allAnnotations, setAllAnnotations] =
|
||||
useState<Highlight[] | undefined>(undefined)
|
||||
const [deletedAnnotations, setDeletedAnnotations] =
|
||||
useState<Highlight[] | undefined>(undefined)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
props.onClose(allAnnotations ?? [], deletedAnnotations ?? [])
|
||||
}, [allAnnotations, deletedAnnotations])
|
||||
|
||||
const handleAnnotationsChange = useCallback(
|
||||
(allAnnotations, deletedAnnotations) => {
|
||||
setAllAnnotations(allAnnotations)
|
||||
setDeletedAnnotations(deletedAnnotations)
|
||||
},
|
||||
[]
|
||||
)
|
||||
const [, updateState] = useState({})
|
||||
|
||||
const exportHighlights = useCallback(() => {
|
||||
;(async () => {
|
||||
if (!props.highlights) {
|
||||
if (!allAnnotations) {
|
||||
showErrorToast('No highlights to export')
|
||||
return
|
||||
}
|
||||
const markdown = highlightsAsMarkdown(props.highlights)
|
||||
const markdown = highlightsAsMarkdown(allAnnotations)
|
||||
await navigator.clipboard.writeText(markdown)
|
||||
showSuccessToast('Highlight copied')
|
||||
})()
|
||||
}, [props.highlights])
|
||||
}, [allAnnotations])
|
||||
|
||||
const sortedHighlights = useMemo(() => {
|
||||
const sorted = (a: number, b: number) => {
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return props.highlights.sort((a: Highlight, b: Highlight) => {
|
||||
if (a.highlightPositionPercent && b.highlightPositionPercent) {
|
||||
return sorted(a.highlightPositionPercent, b.highlightPositionPercent)
|
||||
}
|
||||
// We do this in a try/catch because it might be an invalid diff
|
||||
// With PDF it will definitely be an invalid diff.
|
||||
try {
|
||||
const aPos = getHighlightLocation(a.patch)
|
||||
const bPos = getHighlightLocation(b.patch)
|
||||
if (aPos && bPos) {
|
||||
return sorted(aPos, bPos)
|
||||
}
|
||||
} catch {}
|
||||
return a.createdAt.localeCompare(b.createdAt)
|
||||
})
|
||||
}, [props.highlights])
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
props.viewHighlightInReader(highlightId)
|
||||
handleClose()
|
||||
},
|
||||
[props, handleClose]
|
||||
)
|
||||
|
||||
return (
|
||||
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
|
||||
<ModalRoot defaultOpen onOpenChange={handleClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
onPointerDownOutside={(event) => {
|
||||
onInteractOutside={(event) => {
|
||||
event.preventDefault()
|
||||
props.onOpenChange(false)
|
||||
}}
|
||||
css={{ overflow: 'auto', px: '24px' }}
|
||||
css={{
|
||||
overflow: 'auto',
|
||||
height: sizeMode === 'normal' ? 'unset' : '100%',
|
||||
maxWidth: sizeMode === 'normal' ? '640px' : '100%',
|
||||
minHeight: sizeMode === 'normal' ? '525px' : 'unset',
|
||||
'@mdDown': {
|
||||
top: '20px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxHeight: 'unset',
|
||||
transform: 'translate(-50%)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack distribution="start" css={{ height: '100%' }}>
|
||||
<HStack
|
||||
distribution="between"
|
||||
alignment="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
position: 'sticky',
|
||||
top: '0px',
|
||||
height: '50px',
|
||||
p: '20px',
|
||||
bg: '$grayBg',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<StyledText style="modalHeadline" css={{ color: '$thTextSubtle2' }}>
|
||||
Notebook
|
||||
</StyledText>
|
||||
<HStack
|
||||
distribution="between"
|
||||
css={{
|
||||
ml: 'auto',
|
||||
cursor: 'pointer',
|
||||
gap: '15px',
|
||||
mr: '-5px',
|
||||
}}
|
||||
distribution="center"
|
||||
alignment="center"
|
||||
css={{ height: '50px', width: '100%' }}
|
||||
>
|
||||
<StyledText style="modalHeadline">Notebook</StyledText>
|
||||
<HStack css={{ ml: 'auto', gap: '10px' }}>
|
||||
<Dropdown triggerElement={<MenuTrigger />}>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlights()
|
||||
}}
|
||||
title="Export"
|
||||
/>
|
||||
</Dropdown>
|
||||
<CloseButton close={() => props.onOpenChange(false)} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Box css={{ overflow: 'auto', width: '100%' }}>
|
||||
{sortedHighlights.map((highlight) => (
|
||||
<ModalHighlightView
|
||||
key={highlight.id}
|
||||
highlight={highlight}
|
||||
showDelete={!!props.deleteHighlightAction}
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
setSetLabelsTarget={setLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={
|
||||
setShowConfirmDeleteHighlightId
|
||||
}
|
||||
deleteHighlightAction={() => {
|
||||
if (props.deleteHighlightAction) {
|
||||
props.deleteHighlightAction(highlight.id)
|
||||
}
|
||||
<SizeToggle mode={sizeMode} setMode={setSizeMode} />
|
||||
<Dropdown triggerElement={<MenuTrigger />}>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlights()
|
||||
}}
|
||||
updateHighlight={props.updateHighlight}
|
||||
title="Export Notebook"
|
||||
/>
|
||||
))}
|
||||
{sortedHighlights.length === 0 && (
|
||||
<SpanBox css={{ textAlign: 'center', width: '100%' }}>
|
||||
<StyledText css={{ mb: '40px' }}>
|
||||
You have not added any highlights or notes to this document
|
||||
</StyledText>
|
||||
</SpanBox>
|
||||
)}
|
||||
</Box>
|
||||
</VStack>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
setShowConfirmDeleteNote(true)
|
||||
}}
|
||||
title="Delete Document Note"
|
||||
/>
|
||||
</Dropdown>
|
||||
<CloseButton close={handleClose} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Notebook
|
||||
{...props}
|
||||
sizeMode={sizeMode}
|
||||
viewInReader={viewInReader}
|
||||
onAnnotationsChanged={handleAnnotationsChange}
|
||||
/>
|
||||
</ModalContent>
|
||||
{showConfirmDeleteHighlightId && (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={() => {
|
||||
if (props.deleteHighlightAction) {
|
||||
props.deleteHighlightAction(showConfirmDeleteHighlightId)
|
||||
}
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
icon={
|
||||
<TrashIcon
|
||||
size={40}
|
||||
strokeColor={theme.colors.grayTextContrast.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{labelsTarget && (
|
||||
<SetLabelsModal
|
||||
provider={labelsTarget}
|
||||
onOpenChange={function (open: boolean): void {
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
onLabelsUpdated={function (labels: Label[]): void {
|
||||
updateState({})
|
||||
}}
|
||||
save={function (labels: Label[]): Promise<Label[] | undefined> {
|
||||
const result = setLabelsForHighlight(
|
||||
labelsTarget.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
return result
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ModalRoot>
|
||||
)
|
||||
}
|
||||
|
||||
type ModalHighlightViewProps = {
|
||||
highlight: Highlight
|
||||
showDelete: boolean
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
deleteHighlightAction: () => void
|
||||
updateHighlight: (highlight: Highlight) => void
|
||||
|
||||
setSetLabelsTarget: (highlight: Highlight) => void
|
||||
setShowConfirmDeleteHighlightId: (id: string | undefined) => void
|
||||
type SizeToggleProps = {
|
||||
mode: 'normal' | 'maximized'
|
||||
setMode: (mode: 'normal' | 'maximized') => void
|
||||
}
|
||||
|
||||
function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
|
||||
const [hover, setHover] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
const copyHighlight = useCallback(async () => {
|
||||
await navigator.clipboard.writeText(props.highlight.quote)
|
||||
}, [props.highlight])
|
||||
|
||||
function CloseButton(props: { close: () => void }): JSX.Element {
|
||||
return (
|
||||
<HStack
|
||||
css={{ width: '100%', py: '20px', cursor: 'pointer' }}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
<Button
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
padding: '3px',
|
||||
alignItems: 'center',
|
||||
borderRadius: '9999px',
|
||||
'&:hover': {
|
||||
bg: '#898989',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
props.close()
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<VStack>
|
||||
{/* <SpanBox css={{ marginLeft: 'auto' }}>
|
||||
<Dropdown
|
||||
triggerElement={
|
||||
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
|
||||
}
|
||||
>
|
||||
<DropdownOption
|
||||
onSelect={async () => {
|
||||
await copyHighlight()
|
||||
}}
|
||||
title="Copy"
|
||||
/>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
props.setSetLabelsTarget(props.highlight)
|
||||
}}
|
||||
title="Labels"
|
||||
/>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
props.setShowConfirmDeleteHighlightId(props.highlight.id)
|
||||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
</Dropdown>
|
||||
</SpanBox> */}
|
||||
|
||||
<HighlightView
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
highlight={props.highlight}
|
||||
/>
|
||||
{!isEditing ? (
|
||||
<StyledText
|
||||
css={{
|
||||
borderRadius: '5px',
|
||||
p: '16px',
|
||||
width: '100%',
|
||||
marginTop: '24px',
|
||||
bg: '#EBEBEB',
|
||||
color: '#3D3D3D',
|
||||
}}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
{props.highlight.annotation
|
||||
? props.highlight.annotation
|
||||
: 'Add your notes...'}
|
||||
</StyledText>
|
||||
) : null}
|
||||
{isEditing && (
|
||||
<HighlightNoteTextEditArea
|
||||
setIsEditing={setIsEditing}
|
||||
highlight={props.highlight}
|
||||
updateHighlight={props.updateHighlight}
|
||||
/>
|
||||
)}
|
||||
<SpanBox css={{ mt: '$2', mb: '$4' }} />
|
||||
</VStack>
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
width: '20px',
|
||||
visibility: hover ? 'unset' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HighlightsMenu
|
||||
highlight={props.highlight}
|
||||
setLabelsTarget={props.setSetLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={
|
||||
props.setShowConfirmDeleteHighlightId
|
||||
}
|
||||
/>
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
<X
|
||||
width={17}
|
||||
height={17}
|
||||
color={theme.colors.thTextContrast2.toString()}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function SizeToggle(props: SizeToggleProps): JSX.Element {
|
||||
return (
|
||||
<Button
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
padding: '2px',
|
||||
alignItems: 'center',
|
||||
borderRadius: '9999px',
|
||||
'&:hover': {
|
||||
bg: '#898989',
|
||||
},
|
||||
'@mdDown': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
props.setMode(props.mode == 'normal' ? 'maximized' : 'normal')
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.mode == 'normal' ? (
|
||||
<ArrowsOut size="15" color={theme.colors.thTextContrast2.toString()} />
|
||||
) : (
|
||||
<ArrowsIn size="15" color={theme.colors.thTextContrast2.toString()} />
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticle
|
|||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { isDarkTheme } from '../../../lib/themeUpdater'
|
||||
import PSPDFKit from 'pspdfkit'
|
||||
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
|
||||
|
|
@ -12,15 +12,15 @@ import { deleteHighlightMutation } from '../../../lib/networking/mutations/delet
|
|||
import { articleReadingProgressMutation } from '../../../lib/networking/mutations/articleReadingProgressMutation'
|
||||
import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation'
|
||||
import { useCanShareNative } from '../../../lib/hooks/useCanShareNative'
|
||||
import { webBaseURL } from '../../../lib/appConfig'
|
||||
import { pspdfKitKey } from '../../../lib/appConfig'
|
||||
import { NotebookModal } from './NotebookModal'
|
||||
import { HighlightNoteModal } from './HighlightNoteModal'
|
||||
import { showErrorToast } from '../../../lib/toastHelpers'
|
||||
import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from '../homeFeed/HeaderSpacer'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
export type PdfArticleContainerProps = {
|
||||
viewerUsername: string
|
||||
viewer: UserBasicData
|
||||
article: ArticleAttributes
|
||||
showHighlightsModal: boolean
|
||||
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
|
||||
|
|
@ -30,43 +30,41 @@ export default function PdfArticleContainer(
|
|||
props: PdfArticleContainerProps
|
||||
): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const [shareTarget, setShareTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [shareTarget, setShareTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
const [notebookKey, setNotebookKey] = useState<string>(uuidv4())
|
||||
const [noteTarget, setNoteTarget] = useState<Highlight | undefined>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] = useState<
|
||||
number | undefined
|
||||
>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] =
|
||||
useState<number | undefined>(undefined)
|
||||
const highlightsRef = useRef<Highlight[]>([])
|
||||
const canShareNative = useCanShareNative()
|
||||
|
||||
const getHighlightURL = useCallback(
|
||||
(highlightID: string): string =>
|
||||
`${webBaseURL}/${props.viewerUsername}/${props.article.slug}/highlights/${highlightID}`,
|
||||
[props.article.slug, props.viewerUsername]
|
||||
)
|
||||
// const getHighlightURL = useCallback(
|
||||
// (highlightID: string): string =>
|
||||
// `${webBaseURL}/${props.viewerUsername}/${props.article.slug}/highlights/${highlightID}`,
|
||||
// [props.article.slug, props.viewerUsername]
|
||||
// )
|
||||
|
||||
const nativeShare = useCallback(
|
||||
async (highlightID: string, title: string) => {
|
||||
await navigator?.share({
|
||||
title: title,
|
||||
url: getHighlightURL(highlightID),
|
||||
})
|
||||
},
|
||||
[getHighlightURL]
|
||||
)
|
||||
// const nativeShare = useCallback(
|
||||
// async (highlightID: string, title: string) => {
|
||||
// await navigator?.share({
|
||||
// title: title,
|
||||
// url: getHighlightURL(highlightID),
|
||||
// })
|
||||
// },
|
||||
// [getHighlightURL]
|
||||
// )
|
||||
|
||||
const handleOpenShare = useCallback(
|
||||
(highlight: Highlight) => {
|
||||
if (canShareNative) {
|
||||
nativeShare(highlight.shortId, props.article.title)
|
||||
} else {
|
||||
setShareTarget(highlight)
|
||||
}
|
||||
},
|
||||
[nativeShare, canShareNative, props.article.title]
|
||||
)
|
||||
// const handleOpenShare = useCallback(
|
||||
// (highlight: Highlight) => {
|
||||
// if (canShareNative) {
|
||||
// nativeShare(highlight.shortId, props.article.title)
|
||||
// } else {
|
||||
// setShareTarget(highlight)
|
||||
// }
|
||||
// },
|
||||
// [nativeShare, canShareNative, props.article.title]
|
||||
// )
|
||||
|
||||
const annotationOmnivoreId = (annotation: Annotation): string | undefined => {
|
||||
if (
|
||||
|
|
@ -178,23 +176,23 @@ export default function PdfArticleContainer(
|
|||
instance.setSelectedAnnotation(null)
|
||||
},
|
||||
}
|
||||
const share = {
|
||||
type: 'custom' as const,
|
||||
title: 'Share',
|
||||
id: 'tooltip-share-annotation',
|
||||
className: 'TooltipItem-Share',
|
||||
onPress: () => {
|
||||
if (
|
||||
annotation.customData &&
|
||||
annotation.customData.omnivoreHighlight &&
|
||||
(annotation.customData.omnivoreHighlight as Highlight).shortId
|
||||
) {
|
||||
const data = annotation.customData.omnivoreHighlight as Highlight
|
||||
handleOpenShare(data)
|
||||
}
|
||||
instance.setSelectedAnnotation(null)
|
||||
},
|
||||
}
|
||||
// const share = {
|
||||
// type: 'custom' as const,
|
||||
// title: 'Share',
|
||||
// id: 'tooltip-share-annotation',
|
||||
// className: 'TooltipItem-Share',
|
||||
// onPress: () => {
|
||||
// if (
|
||||
// annotation.customData &&
|
||||
// annotation.customData.omnivoreHighlight &&
|
||||
// (annotation.customData.omnivoreHighlight as Highlight).shortId
|
||||
// ) {
|
||||
// const data = annotation.customData.omnivoreHighlight as Highlight
|
||||
// handleOpenShare(data)
|
||||
// }
|
||||
// instance.setSelectedAnnotation(null)
|
||||
// },
|
||||
// }
|
||||
return [copy, note, remove]
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +235,9 @@ export default function PdfArticleContainer(
|
|||
|
||||
// Store the highlights in the highlightsRef and apply them to the PDF
|
||||
highlightsRef.current = props.article.highlights
|
||||
for (const highlight of props.article.highlights) {
|
||||
for (const highlight of props.article.highlights.filter(
|
||||
(h) => h.type == 'HIGHLIGHT'
|
||||
)) {
|
||||
const patch = JSON.parse(highlight.patch)
|
||||
if (highlight.annotation && patch.customData.omnivoreHighight) {
|
||||
patch.customData.omnivoreHighight.annotation = highlight.annotation
|
||||
|
|
@ -491,15 +491,26 @@ export default function PdfArticleContainer(
|
|||
{props.showHighlightsModal && (
|
||||
<NotebookModal
|
||||
key={notebookKey}
|
||||
viewer={props.viewer}
|
||||
item={props.article}
|
||||
highlights={highlightsRef.current}
|
||||
onOpenChange={() => props.setShowHighlightsModal(false)}
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
updateHighlight={() => {}}
|
||||
deleteHighlightAction={(highlightId: string) => {
|
||||
const event = new CustomEvent('deleteHighlightbyId', {
|
||||
detail: highlightId,
|
||||
onClose={(updatedHighlights, deletedAnnotations) => {
|
||||
console.log(
|
||||
'closed PDF notebook: ',
|
||||
updatedHighlights,
|
||||
deletedAnnotations
|
||||
)
|
||||
deletedAnnotations.forEach((highlight) => {
|
||||
const event = new CustomEvent('deleteHighlightbyId', {
|
||||
detail: highlight.id,
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
viewHighlightInReader={(highlightId) => {
|
||||
// TODO: scroll to highlight in PDF
|
||||
props.setShowHighlightsModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,210 +1,61 @@
|
|||
import { styled } from '@stitches/react'
|
||||
import { Item } from '@radix-ui/react-dropdown-menu'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { DotsThreeVertical } from 'phosphor-react'
|
||||
import { Fragment, useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { Label } from '../../../lib/networking/fragments/labelFragment'
|
||||
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
|
||||
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
|
||||
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
import { HighlightNoteTextEditArea } from '../../elements/HighlightNoteTextEditArea'
|
||||
import { LabelChip } from '../../elements/LabelChip'
|
||||
import {
|
||||
Blockquote,
|
||||
Box,
|
||||
HStack,
|
||||
SpanBox,
|
||||
VStack,
|
||||
} from '../../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { SetLabelsModal } from '../article/SetLabelsModal'
|
||||
Dropdown,
|
||||
DropdownOption,
|
||||
DropdownSeparator,
|
||||
} from '../../elements/DropdownElements'
|
||||
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
|
||||
type HighlightItemProps = {
|
||||
highlight: Highlight
|
||||
viewer: UserBasicData | undefined
|
||||
item: LibraryItemNode
|
||||
|
||||
deleteHighlight: (item: LibraryItemNode, highlight: Highlight) => void
|
||||
}
|
||||
|
||||
const StyledQuote = styled(Blockquote, {
|
||||
margin: '0px',
|
||||
fontSize: '16px',
|
||||
fontFamily: '$inter',
|
||||
fontWeight: '500',
|
||||
lineHeight: '1.50',
|
||||
color: '$thHighContrast',
|
||||
paddingLeft: '15px',
|
||||
borderLeft: '2px solid $omnivoreCtaYellow',
|
||||
})
|
||||
|
||||
export function HighlightItem(props: HighlightItemProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [hover, setHover] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
const lines = useMemo(
|
||||
() => props.highlight.quote.split('\n'),
|
||||
[props.highlight.quote]
|
||||
)
|
||||
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
|
||||
useState<undefined | string>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [, updateState] = useState({})
|
||||
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
css={{ width: '100%', py: '20px', cursor: 'pointer' }}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<VStack
|
||||
css={{
|
||||
gap: '10px',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
|
||||
wordBreak: 'break-word',
|
||||
overflow: 'clip',
|
||||
}}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
>
|
||||
<StyledQuote
|
||||
onClick={(event) => {
|
||||
if (router && props.viewer) {
|
||||
const dest = `/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`
|
||||
router.push(dest)
|
||||
}
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<SpanBox css={{ p: '1px', borderRadius: '2px' }}>
|
||||
{lines.map((line: string, index: number) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
{index !== lines.length - 1 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</SpanBox>
|
||||
</StyledQuote>
|
||||
|
||||
<Box css={{ display: 'block' }}>
|
||||
{props.highlight.labels?.map((label: Label, index: number) => (
|
||||
<LabelChip
|
||||
key={index}
|
||||
text={label.name || ''}
|
||||
color={label.color}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{!isEditing && (
|
||||
<StyledText
|
||||
css={{
|
||||
borderRadius: '6px',
|
||||
bg: '#EBEBEB',
|
||||
p: '10px',
|
||||
width: '100%',
|
||||
marginTop: '5px',
|
||||
color: '#3D3D3D',
|
||||
}}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
{props.highlight.annotation
|
||||
? props.highlight.annotation
|
||||
: 'Add your notes...'}
|
||||
</StyledText>
|
||||
)}
|
||||
{isEditing && (
|
||||
<HighlightNoteTextEditArea
|
||||
setIsEditing={setIsEditing}
|
||||
highlight={props.highlight}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
updateHighlight={() => {}}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
width: '20px',
|
||||
}}
|
||||
>
|
||||
<HighlightsMenu
|
||||
highlight={props.highlight}
|
||||
setLabelsTarget={setLabelsTarget}
|
||||
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
|
||||
/>
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
{showConfirmDeleteHighlightId && (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={async () => {
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
const result = await deleteHighlightMutation(
|
||||
showConfirmDeleteHighlightId
|
||||
)
|
||||
if (result) {
|
||||
showSuccessToast('Highlight deleted')
|
||||
props.deleteHighlight(props.item, props.highlight)
|
||||
} else {
|
||||
showErrorToast('Error deleting highlight')
|
||||
}
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
/>
|
||||
)}
|
||||
{labelsTarget && (
|
||||
<SetLabelsModal
|
||||
provider={labelsTarget}
|
||||
onOpenChange={function (open: boolean): void {
|
||||
setLabelsTarget(undefined)
|
||||
}}
|
||||
onLabelsUpdated={function (labels: Label[]): void {
|
||||
updateState({})
|
||||
}}
|
||||
save={function (labels: Label[]): Promise<Label[] | undefined> {
|
||||
const result = setLabelsForHighlight(
|
||||
labelsTarget.id,
|
||||
labels.map((label) => label.id)
|
||||
)
|
||||
return result
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
|
||||
type HighlightsMenuProps = {
|
||||
viewer: UserBasicData
|
||||
|
||||
item: ReadableItem
|
||||
highlight: Highlight
|
||||
|
||||
viewInReader: (highlightId: string) => void
|
||||
|
||||
setLabelsTarget: (target: Highlight) => void
|
||||
setShowConfirmDeleteHighlightId: (set: string) => void
|
||||
}
|
||||
|
||||
const StyledLinkItem = styled('a', {
|
||||
display: 'flex',
|
||||
fontSize: '14px',
|
||||
fontWeight: '400',
|
||||
py: '10px',
|
||||
px: '15px',
|
||||
borderRadius: 3,
|
||||
cursor: 'pointer',
|
||||
color: '$utilityTextDefault',
|
||||
textDecoration: 'none',
|
||||
|
||||
'&:hover': {
|
||||
outline: 'none',
|
||||
backgroundColor: '$grayBgHover',
|
||||
},
|
||||
})
|
||||
|
||||
export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
||||
const copyHighlight = useCallback(() => {
|
||||
;(async () => {
|
||||
await navigator.clipboard.writeText(props.highlight.quote)
|
||||
showSuccessToast('Highlight copied')
|
||||
})()
|
||||
const quote = props.highlight.quote
|
||||
if (quote) {
|
||||
;(async () => {
|
||||
await navigator.clipboard.writeText(quote)
|
||||
showSuccessToast('Highlight copied')
|
||||
})()
|
||||
} else {
|
||||
showErrorToast('No highlight text.')
|
||||
}
|
||||
}, [props.highlight])
|
||||
|
||||
return (
|
||||
|
|
@ -249,6 +100,28 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
<DropdownSeparator />
|
||||
<Link
|
||||
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
||||
>
|
||||
<StyledLinkItem
|
||||
onClick={(event) => {
|
||||
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(
|
||||
`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`,
|
||||
'_blank'
|
||||
)
|
||||
return
|
||||
}
|
||||
props.viewInReader(props.highlight.id)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
View In Reader
|
||||
</StyledLinkItem>
|
||||
</Link>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
|
@ -263,9 +136,17 @@ export function highlightAsMarkdown(highlight: Highlight) {
|
|||
}
|
||||
|
||||
export function highlightsAsMarkdown(highlights: Highlight[]) {
|
||||
return highlights
|
||||
const noteMD = highlights.find((h) => h.type == 'NOTE')
|
||||
|
||||
const highlightMD = highlights
|
||||
.filter((h) => h.type == 'HIGHLIGHT')
|
||||
.map((highlight) => {
|
||||
return highlightAsMarkdown(highlight)
|
||||
})
|
||||
.join('\n\n')
|
||||
|
||||
if (noteMD) {
|
||||
return `${noteMD.annotation}\n\n${highlightMD}`
|
||||
}
|
||||
return highlightMD
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { HighlighterCircle } from 'phosphor-react'
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
|
|
@ -18,9 +19,10 @@ import {
|
|||
timeAgo,
|
||||
} from '../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard'
|
||||
import { Notebook } from '../article/Notebook'
|
||||
import { EmptyHighlights } from './EmptyHighlights'
|
||||
import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from './HeaderSpacer'
|
||||
import { HighlightItem, highlightsAsMarkdown } from './HighlightItem'
|
||||
import { highlightsAsMarkdown } from './HighlightItem'
|
||||
|
||||
type HighlightItemsLayoutProps = {
|
||||
items: LibraryItem[]
|
||||
|
|
@ -32,9 +34,8 @@ type HighlightItemsLayoutProps = {
|
|||
export function HighlightItemsLayout(
|
||||
props: HighlightItemsLayoutProps
|
||||
): JSX.Element {
|
||||
const [currentItem, setCurrentItem] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [currentItem, setCurrentItem] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
|
||||
const listReducer = (
|
||||
state: LibraryItem[],
|
||||
|
|
@ -124,6 +125,9 @@ export function HighlightItemsLayout(
|
|||
'@xlgDown': {
|
||||
height: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
|
||||
},
|
||||
'@lgDown': {
|
||||
overflowY: 'scroll',
|
||||
},
|
||||
bg: '$thBackground2',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
|
|
@ -165,7 +169,7 @@ export function HighlightItemsLayout(
|
|||
borderBottom: '1px solid $thBorderColor',
|
||||
}}
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
distribution="center"
|
||||
></HStack>
|
||||
<LibraryItemsList
|
||||
items={items}
|
||||
|
|
@ -185,28 +189,19 @@ export function HighlightItemsLayout(
|
|||
height: '100%',
|
||||
width: '100%',
|
||||
flexGrow: '1',
|
||||
justifyContent: 'center',
|
||||
overflowY: 'scroll',
|
||||
'@lgDown': {
|
||||
display: 'none',
|
||||
flexGrow: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HStack
|
||||
css={{
|
||||
flexGrow: '1',
|
||||
overflowY: 'scroll',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
distribution="start"
|
||||
alignment="start"
|
||||
>
|
||||
<HighlightList
|
||||
item={currentItem}
|
||||
viewer={props.viewer}
|
||||
deleteHighlight={handleDelete}
|
||||
/>
|
||||
</HStack>
|
||||
<HighlightList
|
||||
item={currentItem}
|
||||
viewer={props.viewer}
|
||||
deleteHighlight={handleDelete}
|
||||
/>
|
||||
</SpanBox>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -256,6 +251,7 @@ function LibraryItemsList(props: LibraryItemsListProps): JSX.Element {
|
|||
)}
|
||||
</Box>
|
||||
))}
|
||||
<Box css={{ height: '240px' }} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -366,6 +362,8 @@ type HighlightListProps = {
|
|||
}
|
||||
|
||||
function HighlightList(props: HighlightListProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
|
||||
const exportHighlights = useCallback(() => {
|
||||
;(async () => {
|
||||
if (!props.item.node.highlights) {
|
||||
|
|
@ -378,68 +376,231 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
|||
})()
|
||||
}, [props.item.node.highlights])
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
if (!router || !router.isReady || !props.viewer) {
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
}
|
||||
console.log(
|
||||
'pushing user: ',
|
||||
props.viewer,
|
||||
'slug: ',
|
||||
props.item.node.slug
|
||||
)
|
||||
router.push(
|
||||
{
|
||||
pathname: '/[username]/[slug]',
|
||||
query: {
|
||||
username: props.viewer.profile.username,
|
||||
slug: props.item.node.slug,
|
||||
},
|
||||
hash: highlightId,
|
||||
},
|
||||
`${props.viewer.profile.username}/${props.item.node.slug}#${highlightId}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
)
|
||||
},
|
||||
[router, props]
|
||||
)
|
||||
|
||||
return (
|
||||
<HStack
|
||||
<VStack
|
||||
css={{
|
||||
m: '20px',
|
||||
height: '100%',
|
||||
flexGrow: '1',
|
||||
height: '100%',
|
||||
minWidth: '425px',
|
||||
maxWidth: '625px',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
distribution="center"
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
alignment="center"
|
||||
>
|
||||
<VStack
|
||||
<HStack
|
||||
css={{
|
||||
width: '425px',
|
||||
borderRadius: '6px',
|
||||
width: '100%',
|
||||
borderBottom: '1px solid $thBorderColor',
|
||||
}}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
distribution="center"
|
||||
>
|
||||
<HStack
|
||||
<StyledText
|
||||
css={{
|
||||
fontWeight: '600',
|
||||
fontSize: '15px',
|
||||
fontFamily: '$display',
|
||||
width: '100%',
|
||||
pt: '25px',
|
||||
borderBottom: '1px solid $thBorderColor',
|
||||
color: 'thTextContrast2',
|
||||
m: '0px',
|
||||
pb: '5px',
|
||||
}}
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
>
|
||||
<StyledText
|
||||
css={{
|
||||
fontWeight: '600',
|
||||
fontSize: '15px',
|
||||
fontFamily: '$display',
|
||||
width: '100%',
|
||||
color: 'thTextContrast2',
|
||||
NOTEBOOK
|
||||
</StyledText>
|
||||
<Dropdown triggerElement={<MenuTrigger />}>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlights()
|
||||
}}
|
||||
>
|
||||
HIGHLIGHTS
|
||||
</StyledText>
|
||||
<Dropdown triggerElement={<MenuTrigger />}>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlights()
|
||||
}}
|
||||
title="Export"
|
||||
/>
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
<VStack css={{ width: '100%' }} distribution="start" alignment="start">
|
||||
{(props.item.node.highlights ?? []).map((highlight) => (
|
||||
<HighlightItem
|
||||
key={highlight.id}
|
||||
viewer={props.viewer}
|
||||
item={props.item.node}
|
||||
highlight={highlight}
|
||||
deleteHighlight={props.deleteHighlight}
|
||||
/>
|
||||
))}
|
||||
<Box css={{ height: '100px' }} />
|
||||
</VStack>
|
||||
</VStack>
|
||||
</HStack>
|
||||
title="Export"
|
||||
/>
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
<HStack css={{ width: '100%', height: '100%' }}>
|
||||
{props.viewer && (
|
||||
<Notebook
|
||||
sizeMode="normal"
|
||||
viewer={props.viewer}
|
||||
item={props.item.node}
|
||||
highlights={props.item.node.highlights ?? []}
|
||||
viewInReader={viewInReader}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <HStack
|
||||
// css={{
|
||||
// m: '20px',
|
||||
// height: '100%',
|
||||
// flexGrow: '1',
|
||||
// }}
|
||||
// distribution="center"
|
||||
// alignment="start"
|
||||
// >
|
||||
// <VStack
|
||||
// css={{
|
||||
// width: '425px',
|
||||
// borderRadius: '6px',
|
||||
// }}
|
||||
// alignment="start"
|
||||
// distribution="start"
|
||||
// >
|
||||
// <HStack
|
||||
// css={{
|
||||
// width: '100%',
|
||||
// pt: '25px',
|
||||
// borderBottom: '1px solid $thBorderColor',
|
||||
// }}
|
||||
// alignment="center"
|
||||
// distribution="center"
|
||||
// >
|
||||
// <StyledText
|
||||
// css={{
|
||||
// fontWeight: '600',
|
||||
// fontSize: '15px',
|
||||
// fontFamily: '$display',
|
||||
// width: '100%',
|
||||
// color: 'thTextContrast2',
|
||||
// }}
|
||||
// >
|
||||
// NOTEBOOK
|
||||
// </StyledText>
|
||||
// <Dropdown triggerElement={<MenuTrigger />}>
|
||||
// <DropdownOption
|
||||
// onSelect={() => {
|
||||
// exportHighlights()
|
||||
// }}
|
||||
// title="Export"
|
||||
// />
|
||||
// </Dropdown>
|
||||
// </HStack>
|
||||
|
||||
// <HStack
|
||||
// css={{
|
||||
// width: '100%',
|
||||
// pt: '25px',
|
||||
// borderBottom: '1px solid $thBorderColor',
|
||||
// }}
|
||||
// alignment="center"
|
||||
// distribution="center"
|
||||
// >
|
||||
// <StyledText
|
||||
// css={{
|
||||
// fontWeight: '600',
|
||||
// fontSize: '15px',
|
||||
// fontFamily: '$display',
|
||||
// width: '100%',
|
||||
// color: 'thTextContrast2',
|
||||
// }}
|
||||
// >
|
||||
// NOTE
|
||||
// </StyledText>
|
||||
// </HStack>
|
||||
// <HighlightNoteBox
|
||||
// sizeMode="normal"
|
||||
// mode={notesEditMode}
|
||||
// setEditMode={setNotesEditMode}
|
||||
// text={note?.annotation}
|
||||
// placeHolder="Add notes to this document..."
|
||||
// saveText={(highlight) => {
|
||||
// console.log('saving text', highlight)
|
||||
// }}
|
||||
// />
|
||||
// <SpanBox css={{ mt: '10px', mb: '25px' }} />
|
||||
|
||||
// {sortedHighlights && (
|
||||
// <>
|
||||
// <HStack
|
||||
// css={{
|
||||
// width: '100%',
|
||||
// pt: '25px',
|
||||
// borderBottom: '1px solid $thBorderColor',
|
||||
// }}
|
||||
// alignment="center"
|
||||
// distribution="center"
|
||||
// >
|
||||
// <StyledText
|
||||
// css={{
|
||||
// fontWeight: '600',
|
||||
// fontSize: '15px',
|
||||
// fontFamily: '$display',
|
||||
// width: '100%',
|
||||
// color: 'thTextContrast2',
|
||||
// }}
|
||||
// >
|
||||
// HIGHLIGHTS
|
||||
// </StyledText>
|
||||
// </HStack>
|
||||
// <VStack
|
||||
// css={{ width: '100%', mt: '20px' }}
|
||||
// distribution="start"
|
||||
// alignment="start"
|
||||
// >
|
||||
// {sortedHighlights.map((highlight) => (
|
||||
// <>
|
||||
// <HighlightViewItem
|
||||
// key={highlight.id}
|
||||
// highlight={highlight}
|
||||
// updateHighlight={(highlight) => {
|
||||
// console.log('updated highlight: ', highlight)
|
||||
// }}
|
||||
|
||||
// deleteHighlightAction={(highlight) => {
|
||||
// console.log('deleting: ', highlight)
|
||||
// }}
|
||||
|
||||
// setSetLabelsTarget: (highlight: Highlight) => void
|
||||
// setShowConfirmDeleteHighlightId: (id: string | undefined) => void
|
||||
|
||||
// />
|
||||
// <SpanBox css={{ mt: '10px', mb: '25px' }} />
|
||||
// </>
|
||||
// ))}
|
||||
// <Box css={{ height: '100px' }} />
|
||||
// </VStack>
|
||||
// <SpanBox css={{ mt: '10px', mb: '25px' }} />
|
||||
// </>
|
||||
// )}
|
||||
// </VStack>
|
||||
// </HStack>
|
||||
// )
|
||||
}
|
||||
|
||||
type HighlightCountChipProps = {
|
||||
|
|
|
|||
|
|
@ -78,9 +78,8 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
|
||||
const gridContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
|
||||
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
|
||||
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
|
||||
|
|
@ -701,6 +700,10 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
{...props}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.showAddLinkModal && (
|
||||
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
|
|
@ -916,10 +919,6 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
|
|||
)}
|
||||
</Dropzone>
|
||||
</VStack>
|
||||
|
||||
{props.showAddLinkModal && (
|
||||
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
|
||||
)}
|
||||
{props.showEditTitleModal && (
|
||||
<EditLibraryItemModal
|
||||
updateItem={(item: LibraryItem) =>
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
|||
thBackground2: '#F3F3F3',
|
||||
thBackground3: '#FFFFFF',
|
||||
thBackground4: '#EBEBEB',
|
||||
thBackground5: '#F5F5F5',
|
||||
thBackgroundActive: '#F9F9F9',
|
||||
thBackgroundContrast: '#FFFFFF',
|
||||
|
||||
|
|
@ -254,6 +255,7 @@ const darkThemeSpec = {
|
|||
thBackground2: '#3D3D3D',
|
||||
thBackground3: '#242424',
|
||||
thBackground4: '#3D3D3D',
|
||||
thBackground5: '#3D3D3D',
|
||||
thBackgroundActive: '#2E2E2E',
|
||||
thBackgroundContrast: '#000000',
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ type AppEnvironment = 'prod' | 'dev' | 'demo' | 'local'
|
|||
type BaseURLs = {
|
||||
webBaseURL: string
|
||||
serverBaseURL: string
|
||||
highlightsBaseURL: string
|
||||
}
|
||||
|
||||
type BaseURLRecords = Record<AppEnvironment, BaseURLs>
|
||||
|
|
@ -14,22 +13,18 @@ const baseURLRecords: BaseURLRecords = {
|
|||
prod: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
dev: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_DEV_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_DEV_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_DEV_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
demo: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_DEMO_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_DEMO_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_DEMO_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
local: {
|
||||
webBaseURL: process.env.NEXT_PUBLIC_LOCAL_BASE_URL ?? '',
|
||||
serverBaseURL: process.env.NEXT_PUBLIC_LOCAL_SERVER_BASE_URL ?? '',
|
||||
highlightsBaseURL: process.env.NEXT_PUBLIC_LOCAL_HIGHLIGHTS_BASE_URL ?? '',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -43,16 +38,6 @@ function serverBaseURL(env: AppEnvironment): string {
|
|||
return value
|
||||
}
|
||||
|
||||
function highlightsURL(env: AppEnvironment): string {
|
||||
const value = baseURLRecords[appEnv].highlightsBaseURL
|
||||
if (value.length == 0) {
|
||||
throw new Error(
|
||||
`Couldn't find environment variable for highlights base url in ${env} environment`
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function webURL(env: AppEnvironment): string {
|
||||
const value = baseURLRecords[appEnv].webBaseURL
|
||||
if (value.length == 0) {
|
||||
|
|
@ -96,6 +81,4 @@ export const gqlEndpoint = `${serverBaseURL(appEnv)}/api/graphql`
|
|||
|
||||
export const fetchEndpoint = `${serverBaseURL(appEnv)}/api`
|
||||
|
||||
export const highlightsBaseURL = highlightsURL(appEnv)
|
||||
|
||||
export const webBaseURL = webURL(appEnv)
|
||||
|
|
|
|||
|
|
@ -17,3 +17,10 @@ export function formattedShortDate(rawDate: string): string {
|
|||
timeZone,
|
||||
}).format(new Date(rawDate))
|
||||
}
|
||||
|
||||
export function formattedShortTime(rawDate: string): string {
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
timeStyle: 'short',
|
||||
timeZone,
|
||||
}).format(new Date(rawDate))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { extendRangeToWordBoundaries } from './normalizeHighlightRange'
|
|||
import type { Highlight } from '../networking/fragments/highlightFragment'
|
||||
import { removeHighlights } from './deleteHighlight'
|
||||
import { ArticleMutations } from '../articleActions'
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown'
|
||||
|
||||
type CreateHighlightInput = {
|
||||
selection: SelectionAttributes
|
||||
|
|
@ -28,6 +29,20 @@ type CreateHighlightOutput = {
|
|||
newHighlightIndex?: number
|
||||
}
|
||||
|
||||
/* ********************************************************* *
|
||||
* Re-use
|
||||
* If using it several times, creating an instance saves time
|
||||
* ********************************************************* */
|
||||
const nhm = new NodeHtmlMarkdown(
|
||||
/* options (optional) */ {},
|
||||
/* customTransformers (optional) */ undefined,
|
||||
/* customCodeBlockTranslators (optional) */ undefined
|
||||
)
|
||||
|
||||
export const htmlToMarkdown = (html: string) => {
|
||||
return nhm.translate(/* html */ html)
|
||||
}
|
||||
|
||||
export async function createHighlight(
|
||||
input: CreateHighlightInput,
|
||||
articleMutations: ArticleMutations
|
||||
|
|
@ -42,6 +57,10 @@ export async function createHighlight(
|
|||
|
||||
extendRangeToWordBoundaries(range)
|
||||
|
||||
// Create a temp container for copying the range HTML
|
||||
const container = document.createElement('div')
|
||||
container.appendChild(range.cloneContents())
|
||||
|
||||
const id = uuidv4()
|
||||
const patch = generateDiffPatch(range)
|
||||
|
||||
|
|
@ -79,12 +98,15 @@ export async function createHighlight(
|
|||
)
|
||||
|
||||
const newHighlightAttributes = {
|
||||
prefix: highlightAttributes.prefix,
|
||||
suffix: highlightAttributes.suffix,
|
||||
quote: highlightAttributes.quote,
|
||||
id,
|
||||
shortId: nanoid(8),
|
||||
patch,
|
||||
|
||||
prefix: highlightAttributes.prefix,
|
||||
suffix: highlightAttributes.suffix,
|
||||
quote: htmlToMarkdown(container.innerHTML),
|
||||
html: container.innerHTML,
|
||||
|
||||
annotation: annotations.length > 0 ? annotations.join('\n') : undefined,
|
||||
articleId: input.articleId,
|
||||
highlightPositionPercent: input.highlightPositionPercent,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { HighlightLocation } from './highlightGenerator'
|
||||
import { getHighlightElements, getHighlightNoteButton } from './highlightHelpers'
|
||||
import {
|
||||
getHighlightElements,
|
||||
getHighlightNoteButton,
|
||||
} from './highlightHelpers'
|
||||
|
||||
export function removeHighlights(ids: string[], locations: HighlightLocation[]): void {
|
||||
export function removeHighlights(
|
||||
ids: string[],
|
||||
locations: HighlightLocation[]
|
||||
): void {
|
||||
ids.forEach((id) => {
|
||||
const elements = getHighlightElements(id)
|
||||
const noteButtons = getHighlightNoteButton(id)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Label } from './labelFragment'
|
|||
export const highlightFragment = gql`
|
||||
fragment HighlightFields on Highlight {
|
||||
id
|
||||
type
|
||||
shortId
|
||||
quote
|
||||
prefix
|
||||
|
|
@ -24,11 +25,13 @@ export const highlightFragment = gql`
|
|||
}
|
||||
}
|
||||
`
|
||||
export type HighlightType = 'HIGHLIGHT' | 'REDACTION' | 'NOTE'
|
||||
|
||||
export type Highlight = {
|
||||
id: string
|
||||
type: HighlightType
|
||||
shortId: string
|
||||
quote: string
|
||||
quote?: string
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
patch: string
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
import { gql } from 'graphql-request'
|
||||
import { gqlFetcher } from '../networkHelpers'
|
||||
import { Highlight, highlightFragment } from './../fragments/highlightFragment'
|
||||
import {
|
||||
Highlight,
|
||||
highlightFragment,
|
||||
HighlightType,
|
||||
} from './../fragments/highlightFragment'
|
||||
|
||||
export type CreateHighlightInput = {
|
||||
prefix: string
|
||||
suffix: string
|
||||
quote: string
|
||||
id: string
|
||||
shortId: string
|
||||
patch: string
|
||||
articleId: string
|
||||
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
quote?: string
|
||||
html?: string
|
||||
annotation?: string
|
||||
|
||||
patch?: string
|
||||
|
||||
highlightPositionPercent?: number
|
||||
highlightPositionAnchorIndex?: number
|
||||
|
||||
type?: HighlightType
|
||||
}
|
||||
|
||||
type CreateHighlightOutput = {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export type MergeHighlightInput = {
|
|||
quote: string
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
html?: string
|
||||
annotation?: string
|
||||
overlapHighlightIdList: string[]
|
||||
highlightPositionPercent?: number
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ import { Label } from './../fragments/labelFragment'
|
|||
import { showErrorToast, showSuccessToast } from '../../toastHelpers'
|
||||
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
|
||||
|
||||
export interface ReadableItem {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
export type LibraryItemsQueryInput = {
|
||||
limit: number
|
||||
sortDescending: boolean
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@
|
|||
"downshift": "^6.1.9",
|
||||
"graphql-request": "^3.6.1",
|
||||
"kbar": "^0.1.0-beta.35",
|
||||
"markdown-it": "^13.0.1",
|
||||
"nanoid": "^3.1.29",
|
||||
"next": "^12.1.0",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"phosphor-react": "^1.4.0",
|
||||
"pspdfkit": "^2022.2.3",
|
||||
"react": "^17.0.2",
|
||||
|
|
@ -50,6 +52,8 @@
|
|||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-markdown": "^8.0.6",
|
||||
"react-markdown-editor-lite": "^1.3.4",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-pro-sidebar": "^0.7.1",
|
||||
"react-spinners": "^0.13.7",
|
||||
|
|
@ -77,6 +81,7 @@
|
|||
"@types/diff-match-patch": "^1.0.32",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/segment-analytics": "^0.0.34",
|
||||
|
|
|
|||
|
|
@ -71,8 +71,6 @@ export default function Home(): JSX.Element {
|
|||
|
||||
const actionHandler = useCallback(
|
||||
async (action: string, arg?: unknown) => {
|
||||
console.log('handling action: ', action, article)
|
||||
|
||||
switch (action) {
|
||||
case 'unarchive':
|
||||
if (article) {
|
||||
|
|
@ -348,7 +346,7 @@ export default function Home(): JSX.Element {
|
|||
article={article}
|
||||
showHighlightsModal={showHighlightsModal}
|
||||
setShowHighlightsModal={setShowHighlightsModal}
|
||||
viewerUsername={viewerData.me?.profile?.username}
|
||||
viewer={viewerData.me}
|
||||
/>
|
||||
) : (
|
||||
<VStack
|
||||
|
|
@ -364,10 +362,10 @@ export default function Home(): JSX.Element {
|
|||
>
|
||||
{article && viewerData?.me ? (
|
||||
<ArticleContainer
|
||||
viewer={viewerData.me}
|
||||
article={article}
|
||||
isAppleAppEmbed={false}
|
||||
highlightBarDisabled={false}
|
||||
highlightsBaseURL={`${webBaseURL}/${viewerData.me?.profile?.username}/${slug}/highlights`}
|
||||
fontSize={readerSettings.fontSize}
|
||||
margin={readerSettings.marginWidth}
|
||||
lineHeight={readerSettings.lineHeight}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { mergeHighlightMutation } from '../../../../lib/networking/mutations/mer
|
|||
import { updateHighlightMutation } from '../../../../lib/networking/mutations/updateHighlightMutation'
|
||||
import { articleReadingProgressMutation } from '../../../../lib/networking/mutations/articleReadingProgressMutation'
|
||||
import Script from 'next/script'
|
||||
import { useGetViewerQuery } from '../../../../lib/networking/queries/useGetViewerQuery'
|
||||
|
||||
type AppArticleEmbedContentProps = {
|
||||
slug: string
|
||||
|
|
@ -28,9 +29,8 @@ export default function AppArticleEmbed(): JSX.Element {
|
|||
|
||||
const router = useRouter()
|
||||
|
||||
const [contentProps, setContentProps] = useState<
|
||||
AppArticleEmbedContentProps | undefined
|
||||
>(undefined)
|
||||
const [contentProps, setContentProps] =
|
||||
useState<AppArticleEmbedContentProps | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
|
@ -62,7 +62,7 @@ export default function AppArticleEmbed(): JSX.Element {
|
|||
function AppArticleEmbedContent(
|
||||
props: AppArticleEmbedContentProps
|
||||
): JSX.Element {
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null)
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
|
||||
|
||||
const { articleData } = useGetArticleQuery({
|
||||
|
|
@ -71,7 +71,7 @@ function AppArticleEmbedContent(
|
|||
includeFriendsHighlights: false,
|
||||
})
|
||||
|
||||
if (articleData) {
|
||||
if (articleData && viewerData?.me) {
|
||||
return (
|
||||
<Box>
|
||||
<Script async src="/static/scripts/mathJaxConfiguration.js" />
|
||||
|
|
@ -86,10 +86,10 @@ function AppArticleEmbedContent(
|
|||
className="disable-webkit-callout"
|
||||
>
|
||||
<ArticleContainer
|
||||
viewer={viewerData.me}
|
||||
article={articleData.article.article}
|
||||
isAppleAppEmbed={true}
|
||||
highlightBarDisabled={props.highlightBarDisabled}
|
||||
highlightsBaseURL={`${webBaseURL}/${props.username}/${props.slug}/highlights`}
|
||||
fontSize={props.fontSize}
|
||||
margin={props.margin}
|
||||
fontFamily={props.fontFamily}
|
||||
|
|
|
|||
504
yarn.lock
504
yarn.lock
|
|
@ -1970,6 +1970,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.10"
|
||||
|
||||
"@babel/runtime@^7.6.2":
|
||||
version "7.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.10.4":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
|
||||
|
|
@ -8134,7 +8141,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
||||
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
|
||||
|
||||
"@types/debug@^4.1.0":
|
||||
"@types/debug@^4.0.0", "@types/debug@^4.1.0":
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
|
||||
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
|
||||
|
|
@ -8692,6 +8699,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
||||
|
||||
"@types/prop-types@^15.0.0":
|
||||
version "15.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/qs@*", "@types/qs@^6.9.5":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
|
|
@ -10686,6 +10698,11 @@ bail@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
|
||||
integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==
|
||||
|
||||
bail@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
|
||||
integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
|
@ -11504,6 +11521,11 @@ character-entities@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
|
||||
integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
|
||||
|
||||
character-entities@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
|
||||
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
|
||||
|
||||
character-reference-invalid@^1.0.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
|
||||
|
|
@ -11953,6 +11975,11 @@ comma-separated-tokens@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
|
||||
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
|
||||
|
||||
comma-separated-tokens@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
|
||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||
|
||||
command-score@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381"
|
||||
|
|
@ -12819,7 +12846,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
|
||||
debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
|
|
@ -12868,6 +12895,13 @@ decimal.js@^10.2.1:
|
|||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
|
||||
|
||||
decode-named-character-reference@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
|
||||
integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
|
||||
dependencies:
|
||||
character-entities "^2.0.0"
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
|
||||
|
|
@ -13056,6 +13090,11 @@ deprecation@^2.0.0, deprecation@^2.3.1:
|
|||
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
||||
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
|
||||
|
||||
dequal@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||
|
||||
des.js@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
||||
|
|
@ -13702,6 +13741,11 @@ entities@~2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
entities@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
|
||||
integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
||||
|
|
@ -16170,6 +16214,11 @@ hast-util-to-parse5@^6.0.0:
|
|||
xtend "^4.0.0"
|
||||
zwitch "^1.0.0"
|
||||
|
||||
hast-util-whitespace@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557"
|
||||
integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==
|
||||
|
||||
hastscript@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
|
||||
|
|
@ -17249,6 +17298,11 @@ is-plain-obj@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
|
||||
integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
|
||||
|
||||
is-plain-obj@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
|
||||
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
|
||||
|
||||
is-plain-object@5.0.0, is-plain-object@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
|
||||
|
|
@ -18576,6 +18630,11 @@ kleur@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
kleur@^4.0.3:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
klona@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
|
||||
|
|
@ -18811,6 +18870,13 @@ linkify-it@^3.0.1:
|
|||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
linkify-it@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec"
|
||||
integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
listr-silent-renderer@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
|
||||
|
|
@ -19462,6 +19528,17 @@ markdown-it@^12.3.2:
|
|||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
markdown-it@^13.0.1:
|
||||
version "13.0.1"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430"
|
||||
integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
entities "~3.0.1"
|
||||
linkify-it "^4.0.1"
|
||||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
markdown-to-jsx@^7.1.3:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz#a5f22102fb12241c8cea1ca6a4050bb76b23a25d"
|
||||
|
|
@ -19504,6 +19581,33 @@ mdast-util-definitions@^4.0.0:
|
|||
dependencies:
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdast-util-definitions@^5.0.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"
|
||||
integrity sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
mdast-util-from-markdown@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz#0214124154f26154a2b3f9d401155509be45e894"
|
||||
integrity sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
decode-named-character-reference "^1.0.0"
|
||||
mdast-util-to-string "^3.1.0"
|
||||
micromark "^3.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-decode-string "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
unist-util-stringify-position "^3.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
mdast-util-to-hast@10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb"
|
||||
|
|
@ -19518,11 +19622,32 @@ mdast-util-to-hast@10.0.1:
|
|||
unist-util-position "^3.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdast-util-to-hast@^12.1.0:
|
||||
version "12.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz#045d2825fb04374e59970f5b3f279b5700f6fb49"
|
||||
integrity sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/mdast" "^3.0.0"
|
||||
mdast-util-definitions "^5.0.0"
|
||||
micromark-util-sanitize-uri "^1.1.0"
|
||||
trim-lines "^3.0.0"
|
||||
unist-util-generated "^2.0.0"
|
||||
unist-util-position "^4.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
mdast-util-to-string@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527"
|
||||
integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==
|
||||
|
||||
mdast-util-to-string@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz#db859050d79d48cf9896d294de06f3ede7474d16"
|
||||
integrity sha512-tGvhT94e+cVnQt8JWE9/b3cUQZWS732TJxXHktvP+BYo62PpYD53Ls/6cC60rW21dW+txxiM4zMdc6abASvZKA==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
|
||||
mdurl@^1.0.0, mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
|
|
@ -19644,6 +19769,201 @@ microevent.ts@~0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"
|
||||
integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==
|
||||
|
||||
micromark-core-commonmark@^1.0.1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad"
|
||||
integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==
|
||||
dependencies:
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-factory-destination "^1.0.0"
|
||||
micromark-factory-label "^1.0.0"
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-factory-title "^1.0.0"
|
||||
micromark-factory-whitespace "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-classify-character "^1.0.0"
|
||||
micromark-util-html-tag-name "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-resolve-all "^1.0.0"
|
||||
micromark-util-subtokenize "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.1"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-factory-destination@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e"
|
||||
integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-factory-label@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz#6be2551fa8d13542fcbbac478258fb7a20047137"
|
||||
integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-factory-space@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633"
|
||||
integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-factory-title@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz#7e09287c3748ff1693930f176e1c4a328382494f"
|
||||
integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==
|
||||
dependencies:
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-factory-whitespace@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c"
|
||||
integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==
|
||||
dependencies:
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-character@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86"
|
||||
integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-chunked@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06"
|
||||
integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-classify-character@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20"
|
||||
integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-combine-extensions@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5"
|
||||
integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==
|
||||
dependencies:
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-decode-numeric-character-reference@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946"
|
||||
integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-decode-string@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz#942252ab7a76dec2dbf089cc32505ee2bc3acf02"
|
||||
integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==
|
||||
dependencies:
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-encode@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383"
|
||||
integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==
|
||||
|
||||
micromark-util-html-tag-name@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz#eb227118befd51f48858e879b7a419fc0df20497"
|
||||
integrity sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==
|
||||
|
||||
micromark-util-normalize-identifier@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828"
|
||||
integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-resolve-all@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88"
|
||||
integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==
|
||||
dependencies:
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz#f12e07a85106b902645e0364feb07cf253a85aee"
|
||||
integrity sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-encode "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-subtokenize@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
|
||||
integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==
|
||||
dependencies:
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-util-symbol@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e"
|
||||
integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==
|
||||
|
||||
micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20"
|
||||
integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==
|
||||
|
||||
micromark@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.1.0.tgz#eeba0fe0ac1c9aaef675157b52c166f125e89f62"
|
||||
integrity sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==
|
||||
dependencies:
|
||||
"@types/debug" "^4.0.0"
|
||||
debug "^4.0.0"
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-core-commonmark "^1.0.1"
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-combine-extensions "^1.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-encode "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-resolve-all "^1.0.0"
|
||||
micromark-util-sanitize-uri "^1.0.0"
|
||||
micromark-util-subtokenize "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.1"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||
|
|
@ -20128,6 +20448,11 @@ move-concurrently@^1.0.1:
|
|||
rimraf "^2.5.4"
|
||||
run-queue "^1.0.3"
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
|
||||
|
||||
mrmime@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b"
|
||||
|
|
@ -22260,6 +22585,11 @@ property-information@^5.0.0, property-information@^5.3.0:
|
|||
dependencies:
|
||||
xtend "^4.0.0"
|
||||
|
||||
property-information@^6.0.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d"
|
||||
integrity sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==
|
||||
|
||||
proto-list@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
|
|
@ -23209,6 +23539,42 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-markdown-editor-lite@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-markdown-editor-lite/-/react-markdown-editor-lite-1.3.4.tgz#77992d2389b9427a06595c63d95f52be66e5fea9"
|
||||
integrity sha512-PhS4HzLzSgCsr8O9CfJX75nAYmZ0NwpfviLxARlT0Tau+APOerDSHSw3u9hub5wd0EqmonWibw0vhXXNu4ldRA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
classnames "^2.2.6"
|
||||
eventemitter3 "^4.0.0"
|
||||
uuid "^8.3.2"
|
||||
|
||||
react-markdown@^8.0.6:
|
||||
version "8.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.6.tgz#3e939018f8bfce800ffdf22cf50aba3cdded7ad1"
|
||||
integrity sha512-KgPWsYgHuftdx510wwIzpwf+5js/iHqBR+fzxefv8Khk3mFbnioF1bmL2idHN3ler0LMQmICKeDrWnZrX9mtbQ==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/prop-types" "^15.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
comma-separated-tokens "^2.0.0"
|
||||
hast-util-whitespace "^2.0.0"
|
||||
prop-types "^15.0.0"
|
||||
property-information "^6.0.0"
|
||||
react-is "^18.0.0"
|
||||
remark-parse "^10.0.0"
|
||||
remark-rehype "^10.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
style-to-object "^0.4.0"
|
||||
unified "^10.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
vfile "^5.0.0"
|
||||
|
||||
react-masonry-css@^1.0.16:
|
||||
version "1.0.16"
|
||||
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
|
||||
|
|
@ -23592,7 +23958,7 @@ regenerate@^1.4.2:
|
|||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
||||
|
||||
regenerator-runtime@^0.13.10:
|
||||
regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
|
@ -23781,6 +24147,25 @@ remark-parse@8.0.3:
|
|||
vfile-location "^3.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
remark-parse@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775"
|
||||
integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
mdast-util-from-markdown "^1.0.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remark-rehype@^10.0.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279"
|
||||
integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/mdast" "^3.0.0"
|
||||
mdast-util-to-hast "^12.1.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remark-slug@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-6.1.0.tgz#0503268d5f0c4ecb1f33315c00465ccdd97923ce"
|
||||
|
|
@ -24136,6 +24521,13 @@ rxjs@^7.5.1:
|
|||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
sade@^1.7.3:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
|
||||
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
|
||||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
safe-buffer@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
|
@ -24796,6 +25188,11 @@ space-separated-tokens@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
|
||||
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
|
||||
|
||||
space-separated-tokens@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
|
||||
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
|
||||
|
||||
spark-md5@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
|
||||
|
|
@ -25353,6 +25750,13 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
|
|||
dependencies:
|
||||
inline-style-parser "0.1.1"
|
||||
|
||||
style-to-object@^0.4.0:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.1.tgz#53cf856f7cf7f172d72939d9679556469ba5de37"
|
||||
integrity sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==
|
||||
dependencies:
|
||||
inline-style-parser "0.1.1"
|
||||
|
||||
styled-jsx@5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.2.tgz#ff230fd593b737e9e68b630a694d460425478729"
|
||||
|
|
@ -25985,6 +26389,11 @@ tree-kill@^1.2.2:
|
|||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||
|
||||
trim-lines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
|
||||
integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
|
||||
|
||||
trim-newlines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
|
|
@ -26015,6 +26424,11 @@ trough@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||
|
||||
trough@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||
|
||||
ts-dedent@^2.0.0, ts-dedent@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
|
|
@ -26431,6 +26845,19 @@ unified@9.2.0:
|
|||
trough "^1.0.0"
|
||||
vfile "^4.0.0"
|
||||
|
||||
unified@^10.0.0:
|
||||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df"
|
||||
integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
bail "^2.0.0"
|
||||
extend "^3.0.0"
|
||||
is-buffer "^2.0.0"
|
||||
is-plain-obj "^4.0.0"
|
||||
trough "^2.0.0"
|
||||
vfile "^5.0.0"
|
||||
|
||||
union-value@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
|
||||
|
|
@ -26472,16 +26899,35 @@ unist-util-generated@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b"
|
||||
integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==
|
||||
|
||||
unist-util-generated@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz#e37c50af35d3ed185ac6ceacb6ca0afb28a85cae"
|
||||
integrity sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==
|
||||
|
||||
unist-util-is@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797"
|
||||
integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==
|
||||
|
||||
unist-util-is@^5.0.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9"
|
||||
integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-position@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47"
|
||||
integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==
|
||||
|
||||
unist-util-position@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.4.tgz#93f6d8c7d6b373d9b825844645877c127455f037"
|
||||
integrity sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-remove-position@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc"
|
||||
|
|
@ -26503,6 +26949,13 @@ unist-util-stringify-position@^2.0.0:
|
|||
dependencies:
|
||||
"@types/unist" "^2.0.2"
|
||||
|
||||
unist-util-stringify-position@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
|
||||
integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-visit-parents@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6"
|
||||
|
|
@ -26511,6 +26964,14 @@ unist-util-visit-parents@^3.0.0:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^4.0.0"
|
||||
|
||||
unist-util-visit-parents@^5.1.1:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
|
||||
integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
||||
unist-util-visit@2.0.3, unist-util-visit@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c"
|
||||
|
|
@ -26520,6 +26981,15 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0:
|
|||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
unist-util-visit@^4.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2"
|
||||
integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
unist-util-visit-parents "^5.1.1"
|
||||
|
||||
universal-user-agent@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
|
||||
|
|
@ -26796,6 +27266,16 @@ uuid@^9.0.0:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
uvu@^0.5.0:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
||||
integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==
|
||||
dependencies:
|
||||
dequal "^2.0.0"
|
||||
diff "^5.0.0"
|
||||
kleur "^4.0.3"
|
||||
sade "^1.7.3"
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
|
|
@ -26883,6 +27363,14 @@ vfile-message@^2.0.0:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-stringify-position "^2.0.0"
|
||||
|
||||
vfile-message@^3.0.0:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea"
|
||||
integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-stringify-position "^3.0.0"
|
||||
|
||||
vfile@^4.0.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624"
|
||||
|
|
@ -26893,6 +27381,16 @@ vfile@^4.0.0:
|
|||
unist-util-stringify-position "^2.0.0"
|
||||
vfile-message "^2.0.0"
|
||||
|
||||
vfile@^5.0.0:
|
||||
version "5.3.7"
|
||||
resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7"
|
||||
integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
is-buffer "^2.0.0"
|
||||
unist-util-stringify-position "^3.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
vm-browserify@^1.0.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue