Labels update

This commit is contained in:
Jackson Harper
2023-06-23 17:11:29 +08:00
parent 9f20fd4736
commit fca3a4c2ea
3 changed files with 156 additions and 259 deletions

View File

@ -23,81 +23,81 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
const selectedBorder = isDark ? '#FFEA9F' : 'black'
const unSelectedBorder = isDark ? '#6A6968' : '#D9D9D9'
if (props.useAppAppearance) {
return (
<SpanBox
css={{
display: 'inline-table',
margin: '2px',
fontSize: '11px',
fontWeight: '500',
fontFamily: '$inter',
padding: '4px 10px',
whiteSpace: 'nowrap',
cursor: 'pointer',
backgroundClip: 'padding-box',
borderRadius: '5px',
borderWidth: '1px',
borderStyle: 'solid',
color: isDark ? '#EBEBEB' : '#2A2A2A',
borderColor: props.isSelected ? selectedBorder : unSelectedBorder,
backgroundColor: isDark ? '#2A2A2A' : '#F5F5F5',
}}
>
<HStack alignment="center" css={{ gap: '10px' }}>
<Circle size={14} color={props.color} weight="fill" />
<SpanBox css={{ pt: '1px' }}>{props.text}</SpanBox>
{props.xAction && (
<Button
style="ghost"
css={{ display: 'flex', pt: '1px' }}
onClick={(event) => {
if (props.xAction) {
props.xAction()
event.preventDefault()
}
}}
>
<X
size={14}
color={
props.isSelected
? '#FFEA9F'
: theme.colors.thBorderSubtle.toString()
}
/>
</Button>
)}
</HStack>
</SpanBox>
)
}
// if (props.useAppAppearance) {
return (
<Button
style="plainIcon"
onClick={(e) => {
router.push(`/home?q=label:"${props.text}"`)
e.stopPropagation()
<SpanBox
css={{
display: 'inline-table',
margin: '2px',
fontSize: '11px',
fontWeight: '500',
fontFamily: '$inter',
padding: '1px 7px',
whiteSpace: 'nowrap',
cursor: 'pointer',
backgroundClip: 'padding-box',
borderRadius: '5px',
borderWidth: '1px',
borderStyle: 'solid',
color: isDark ? '#EBEBEB' : '#2A2A2A',
borderColor: props.isSelected ? selectedBorder : unSelectedBorder,
backgroundColor: isDark ? '#2A2A2A' : '#F5F5F5',
}}
>
<SpanBox
css={{
display: 'inline-table',
margin: '2px',
borderRadius: '4px',
color: textColor,
fontSize: '13px',
fontWeight: '500',
padding: '3px 6px',
whiteSpace: 'nowrap',
cursor: 'pointer',
backgroundClip: 'padding-box',
backgroundColor: props.color,
}}
>
{props.text}
</SpanBox>
</Button>
<HStack alignment="center" css={{ gap: '10px' }}>
<Circle size={14} color={props.color} weight="fill" />
<SpanBox css={{ pt: '1px' }}>{props.text}</SpanBox>
{props.xAction && (
<Button
style="ghost"
css={{ display: 'flex', pt: '1px' }}
onClick={(event) => {
if (props.xAction) {
props.xAction()
event.preventDefault()
}
}}
>
<X
size={14}
color={
props.isSelected
? '#FFEA9F'
: theme.colors.thBorderSubtle.toString()
}
/>
</Button>
)}
</HStack>
</SpanBox>
)
// }
// return (
// <Button
// style="plainIcon"
// onClick={(e) => {
// router.push(`/home?q=label:"${props.text}"`)
// e.stopPropagation()
// }}
// >
// <SpanBox
// css={{
// display: 'inline-table',
// margin: '2px',
// borderRadius: '4px',
// color: textColor,
// fontSize: '13px',
// fontWeight: '500',
// padding: '3px 6px',
// whiteSpace: 'nowrap',
// cursor: 'pointer',
// backgroundClip: 'padding-box',
// backgroundColor: props.color,
// }}
// >
// {props.text}
// </SpanBox>
// </Button>
// )
}

View File

@ -2,7 +2,14 @@ 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 {
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
import {
BookOpen,
CaretDown,
@ -50,14 +57,10 @@ export const getHighlightLocation = (patch: string): number | undefined => {
return patches[0].start1 || undefined
}
type AnnotationInfo = {
loaded: boolean
type NoteState = {
isCreating: boolean
note: Highlight | undefined
noteId: string
creatingNote: boolean
allAnnotations: Highlight[]
createStarted: Date | undefined
}
export function NotebookContent(props: NotebookContentProps): JSX.Element {
@ -74,190 +77,59 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>(
'preview'
)
// 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,
// creatingNote: false,
// allAnnotations: [...state.allAnnotations, action.note],
// }
// }
// case 'CREATING_NOTE': {
// return {
// ...state,
// creatingNote: true,
// }
// }
// 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
// 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,
// creatingNote: false,
// noteId: uuidv4(),
// allAnnotations: [],
// deletedAnnotations: [],
// })
// useEffect(() => {
// dispatchAnnotations({
// type: 'RESET',
// allHighlights: props.highlights,
// })
// }, [props.highlights])
// 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 noteReducer = (
state: {
note?: Highlight
isCreating: boolean
},
action: {
type: string
note?: Highlight
}
) => {
switch (action.type) {
case 'SET_NOTE': {
if (!action.note) {
console.error(
'invalidate SET_NOTE action, no note provider',
action,
state
)
}
return {
...state,
note: action.note,
}
}
}
return state
}
const [noteState, dispatchNote] = useReducer(noteReducer, {
note: undefined,
const noteState = useRef<NoteState>({
isCreating: false,
note: undefined,
createStarted: undefined,
})
const newNoteId = useMemo(() => {
return uuidv4()
}, [])
const updateNote = useCallback((note: Highlight, text: string) => {
;(async () => {
const result = await updateHighlightMutation({
highlightId: note.id,
annotation: text,
})
})()
}, [])
const createNote = useCallback((text: string) => {
console.log('creating note: ', newNoteId, noteState.current.isCreating)
noteState.current.isCreating = true
noteState.current.createStarted = new Date()
;(async () => {
try {
const success = await createHighlightMutation({
id: newNoteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.item.id,
annotation: text,
})
if (success) {
noteState.current.note = success
noteState.current.isCreating = false
}
} catch (error) {
console.error('error creating note: ', error)
noteState.current.isCreating = false
}
})()
}, [])
const highlights = useMemo(() => {
const result = articleData?.article.article.highlights
const note = result?.find((h) => h.type === 'NOTE')
if (note) {
dispatchNote({
type: 'SET_NOTE',
note: note,
})
noteState.current.note = note
noteState.current.isCreating = false
}
return result
}, [articleData])
// const note = useMemo(() => {
// return highlights?.find((h) => h.type === 'NOTE')
// }, [highlights])
useEffect(() => {
if (highlights && props.onAnnotationsChanged) {
props.onAnnotationsChanged(highlights)
@ -295,8 +167,30 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
}, [highlights])
const handleSaveNoteText = useCallback(
(text, cb: (success: boolean) => void) => {},
[highlights, props.item]
(text, cb: (success: boolean) => void) => {
console.log('handleSaveNoteText', noteState.current)
if (noteState.current.note) {
updateNote(noteState.current.note, text)
return
}
if (noteState.current.isCreating) {
console.log('note is being created, deferring')
if (noteState.current.createStarted) {
const timeSinceStart =
new Date().getTime() - noteState.current.createStarted.getTime()
console.log(' -- timeSinceStart: ', timeSinceStart)
if (timeSinceStart > 4000) {
createNote(text)
return
}
}
return
}
createNote(text)
},
[noteState, createNote, updateNote]
)
const [articleNotesCollapsed, setArticleNotesCollapsed] = useState(false)
@ -310,7 +204,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
width: '100%',
p: '20px',
'@mdDown': { p: '15px' },
background: '#F8FAFB',
}}
>
<SectionTitle
@ -328,7 +221,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
mode={notesEditMode}
targetId={props.item.id}
setEditMode={setNotesEditMode}
text={noteState.note?.annotation}
text={noteState.current.note?.annotation}
placeHolder="Add notes to this document..."
saveText={handleSaveNoteText}
/>

View File

@ -14,6 +14,7 @@ import {
Recommendation,
recommendationFragment,
} from './useGetLibraryItemsQuery'
import useSWR from 'swr'
type ArticleQueryInput = {
username?: string
@ -25,6 +26,8 @@ type ArticleQueryOutput = {
articleData?: ArticleData
isLoading: boolean
articleFetchError: string[] | null
mutate: () => void
}
type ArticleData = {
@ -107,7 +110,7 @@ export function useGetArticleQuery({
includeFriendsHighlights,
}
const { data, error } = useSWRImmutable(
const { data, error, mutate } = useSWR(
slug ? [query, username, slug, includeFriendsHighlights] : null,
makeGqlFetcher(variables)
)
@ -124,6 +127,7 @@ export function useGetArticleQuery({
}
return {
mutate: mutate,
articleData: resultData,
isLoading: !error && !data,
articleFetchError: resultError ? (resultError as string[]) : null,