From 69eaff395ef693543621c7eb5b04bf385b21ef44 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 23 Mar 2023 09:47:21 +0800 Subject: [PATCH 01/35] WIP: improve notebook UX, add markdown, document notes --- .../elements/HighlightNoteTextEditArea.tsx | 2 +- .../web/components/patterns/HighlightView.tsx | 124 +++- .../templates/article/HighlightHoverCard.tsx | 37 -- .../templates/article/HighlightsLayer.tsx | 19 +- .../templates/article/NotebookModal.tsx | 566 +++++++++++++++--- .../templates/article/PdfArticleContainer.tsx | 5 +- .../templates/homeFeed/HighlightItem.tsx | 14 +- packages/web/lib/dateFormatting.ts | 7 + .../networking/fragments/highlightFragment.ts | 3 + .../mutations/createHighlightMutation.ts | 20 +- packages/web/package.json | 2 + .../web/pages/[username]/[slug]/index.tsx | 2 - yarn.lock | 481 ++++++++++++++- 13 files changed, 1104 insertions(+), 178 deletions(-) delete mode 100644 packages/web/components/templates/article/HighlightHoverCard.tsx diff --git a/packages/web/components/elements/HighlightNoteTextEditArea.tsx b/packages/web/components/elements/HighlightNoteTextEditArea.tsx index 7de604a19..4fad25dc7 100644 --- a/packages/web/components/elements/HighlightNoteTextEditArea.tsx +++ b/packages/web/components/elements/HighlightNoteTextEditArea.tsx @@ -43,7 +43,7 @@ export const HighlightNoteTextEditArea = ( autoFocus maxLength={4000} value={noteContent} - placeholder={'Add your notes...'} + placeholder={'Add notes to this highlight...'} onChange={handleNoteContentChange} /> diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 6141e6962..1f7c8555d 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -1,9 +1,16 @@ -import { Fragment, useMemo } from 'react' +import { Fragment, useMemo, useState } from 'react' import type { Highlight } from '../../lib/networking/fragments/highlightFragment' +import { HighlightNoteTextEditArea } from '../elements/HighlightNoteTextEditArea' import { LabelChip } from '../elements/LabelChip' -import { Box, VStack, Blockquote, SpanBox } from '../elements/LayoutPrimitives' +import { + Box, + VStack, + Blockquote, + SpanBox, + HStack, +} from '../elements/LayoutPrimitives' import { StyledText } from '../elements/StyledText' -import { styled } from '../tokens/stitches.config' +import { styled, theme } from '../tokens/stitches.config' type HighlightViewProps = { highlight: Highlight @@ -17,44 +24,97 @@ const StyledQuote = styled(Blockquote, { fontSize: '18px', lineHeight: '27px', color: '$grayText', - padding: '0px 16px', - borderLeft: '2px solid $omnivoreCtaYellow', }) export function HighlightView(props: HighlightViewProps): JSX.Element { + const [isEditing, setIsEditing] = useState(false) + const lines = useMemo( () => props.highlight.quote.split('\n'), [props.highlight.quote] ) return ( - - { - if (props.scrollToHighlight) { - props.scrollToHighlight(props.highlight.id) - } - }} - > - - {lines.map((line: string, index: number) => ( - - {line} - {index !== lines.length - 1 && ( - <> -
-
- - )} -
+ + + + + + + { + if (props.scrollToHighlight) { + props.scrollToHighlight(props.highlight.id) + } + }} + > + + {lines.map((line: string, index: number) => ( + + {line} + {index !== lines.length - 1 && ( + <> +
+
+ + )} +
+ ))} +
+
+ + {props.highlight.labels?.map(({ name, color }, index) => ( + ))} -
-
- - {props.highlight.labels?.map(({ name, color }, index) => ( - - ))} - -
+ + {!isEditing ? ( + setIsEditing(true)} + > + {props.highlight.annotation + ? props.highlight.annotation + : 'Add notes to this highlight...'} + + ) : null} + {isEditing && ( + {} /* props.updateHighlight */} + /> + )} + +
) } diff --git a/packages/web/components/templates/article/HighlightHoverCard.tsx b/packages/web/components/templates/article/HighlightHoverCard.tsx deleted file mode 100644 index ccaa5a3cd..000000000 --- a/packages/web/components/templates/article/HighlightHoverCard.tsx +++ /dev/null @@ -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 ( - - - - ) -} diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index 675393b70..b583ecd1b 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -121,14 +121,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 @@ -648,6 +650,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { return ( props.setShowHighlightsModal(false)} deleteHighlightAction={(highlightId: string) => { removeHighlightCallback(highlightId) diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 1aca83f46..b825235f2 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -2,7 +2,6 @@ import { ModalRoot, ModalOverlay, ModalContent, - ModalTitleBar, } from '../../elements/ModalPrimitives' import { Box, HStack, VStack, SpanBox } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' @@ -11,10 +10,16 @@ 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 { + ChangeEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import { ConfirmationModal } from '../../patterns/ConfirmationModal' -import { DotsThree } from 'phosphor-react' +import { ArrowsIn, ArrowsOut, BookOpen, PencilLine, X } from 'phosphor-react' import { Dropdown, DropdownOption } from '../../elements/DropdownElements' import { SetLabelsModal } from './SetLabelsModal' import { Label } from '../../../lib/networking/fragments/labelFragment' @@ -22,12 +27,23 @@ import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabe 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 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 { formattedShortTime } from '../../../lib/dateFormatting' +import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation' +import { v4 as uuidv4 } from 'uuid' +import { nanoid } from 'nanoid' +import throttle from 'lodash/throttle' +import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' + +const mdParser = new MarkdownIt() type NotebookModalProps = { + pageId: string highlights: Highlight[] scrollToHighlight?: (arg: string) => void updateHighlight: (highlight: Highlight) => void @@ -47,6 +63,9 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { const [labelsTarget, setLabelsTarget] = useState( undefined ) + const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal') + const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) + const [notesEditMode, setNotesEditMode] = useState(true) const [, updateState] = useState({}) const exportHighlights = useCallback(() => { @@ -61,6 +80,25 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { })() }, [props.highlights]) + const deleteDocumentNote = useCallback(() => { + ;(async () => { + const notes = props.highlights.filter((h) => h.type == 'NOTE') + + notes.forEach(async (n) => { + try { + const result = await deleteHighlightMutation(n.id) + if (!result) { + throw new Error() + } + showSuccessToast('Note deleted') + } catch (err) { + console.log('error deleting note', err) + showErrorToast('Error deleting note') + } + }) + })() + }, []) + const sortedHighlights = useMemo(() => { const sorted = (a: number, b: number) => { if (a < b) { @@ -72,21 +110,23 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { 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) + return props.highlights + .filter((h) => h.type === undefined || 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.highlights]) return ( @@ -97,7 +137,12 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { event.preventDefault() props.onOpenChange(false) }} - css={{ overflow: 'auto', px: '24px' }} + css={{ + overflow: 'auto', + px: '20px', + height: sizeMode === 'normal' ? 'unset' : '100%', + maxWidth: sizeMode === 'normal' ? '640px' : '100%', + }} > - Notebook - + + Notebook + + + }> { exportHighlights() }} - title="Export" + title="Export Notebook" + /> + { + setShowConfirmDeleteNote(true) + }} + title="Delete Document Note" /> props.onOpenChange(false)} /> + + h.type == 'NOTE')} + sizeMode={sizeMode} + mode={notesEditMode ? 'edit' : 'read'} + setEditMode={setNotesEditMode} + /> + + {/* {props.highlights.map((highlight) => ( + + {highlight.annotation} + + + ))} */} + + {sortedHighlights.map((highlight) => ( ))} {sortedHighlights.length === 0 && ( - - - You have not added any highlights or notes to this document - - + + You have not added any highlights to this document. + )} @@ -183,6 +281,14 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { }} /> )} + {showConfirmDeleteNote && ( + deleteDocumentNote()} + onOpenChange={() => setShowConfirmDeleteNote(false)} + /> + )} ) } @@ -202,73 +308,19 @@ 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]) - return ( setHover(true)} onMouseLeave={() => setHover(false)} > - - {/* - - } - > - { - await copyHighlight() - }} - title="Copy" - /> - { - props.setSetLabelsTarget(props.highlight) - }} - title="Labels" - /> - { - props.setShowConfirmDeleteHighlightId(props.highlight.id) - }} - title="Delete" - /> - - */} - + - {!isEditing ? ( - setIsEditing(true)} - > - {props.highlight.annotation - ? props.highlight.annotation - : 'Add your notes...'} - - ) : null} - {isEditing && ( - - )} - + + ) } + +type NoteSectionProps = { + pageId: string + highlight?: Highlight + + sizeMode: 'normal' | 'maximized' + mode: 'edit' | 'read' + setEditMode: (set: boolean) => void +} + +function NoteSection(props: NoteSectionProps): JSX.Element { + const [lastSaved, setLastSaved] = useState(undefined) + const [lastChanged, setLastChanged] = useState(undefined) + const [errorSaving, setErrorSaving] = useState(undefined) + + const [createStartTime, setCreateStartTime] = useState( + undefined + ) + const [createdHighlight, setCreatedHighlight] = useState< + Highlight | undefined + >(undefined) + + const highlightId = useMemo(() => { + if (props.highlight) { + return { id: props.highlight.id, shortId: props.highlight.shortId } + } + return { id: uuidv4(), shortId: nanoid(8) } + }, [props.highlight]) + + const saveText = useCallback( + (text, updateTime) => { + ;(async () => { + console.log('calling save: ', props.highlight, createdHighlight) + if (props.highlight || createdHighlight) { + const success = await updateHighlightMutation({ + annotation: text, + highlightId: + props.highlight?.id ?? + createdHighlight?.id ?? + '' /* impossible to get to but TS thinks it is */, + }) + if (success) { + setLastSaved(updateTime) + } else { + setErrorSaving('Error saving highlight.') + } + } else { + console.log('creating note highlight: ', highlightId) + if (!createStartTime) { + setCreateStartTime(new Date()) + + const created = await createHighlightMutation({ + type: 'NOTE', + id: highlightId.id, + articleId: props.pageId, + shortId: highlightId.shortId, + annotation: text, + }) + console.log('created highlight: ', created) + + if (created) { + setLastSaved(updateTime) + setCreatedHighlight(created) + } else { + console.log('unable to create note highlight') + } + } + } + })() + }, + [lastSaved, lastChanged, createdHighlight, createStartTime] + ) + + const saveRef = useRef(saveText) + + useEffect(() => { + saveRef.current = saveText + }, [lastSaved, lastChanged, createdHighlight, createStartTime]) + + 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 | undefined + ) => { + if (event) { + event.preventDefault() + } + + const updateTime = new Date() + setLastChanged(updateTime) + debouncedSave(data.text, updateTime) + }, + [lastSaved, lastChanged, createdHighlight, createStartTime] + ) + + return ( + <> + {props.mode == 'edit' ? ( + + mdParser.render(text)} + onChange={handleEditorChange} + /> + + {errorSaving && ( + + {errorSaving} + + )} + {lastSaved !== undefined ? ( + <> + {lastChanged === lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} + + ) : null} + + + ) : ( + <> + {props.highlight?.annotation || createdHighlight?.annotation ? ( + + + + ) : ( + + No note on this document, enter edit mode by clicking the pencil + icon above to add a note. + + )} + + )} + + ) +} + +type TitledSectionProps = { + title: string + editMode?: boolean + setEditMode?: (set: boolean) => void +} + +function TitledSection(props: TitledSectionProps): JSX.Element { + return ( + <> + + + {props.title} + + {props.setEditMode && ( + { + if (props.setEditMode) { + props.setEditMode(!props.editMode) + } + event.preventDefault() + }} + > + {props.editMode ? ( + + ) : ( + + )} + + )} + + + + ) +} + +type SizeToggleProps = { + mode: 'normal' | 'maximized' + setMode: (mode: 'normal' | 'maximized') => void +} + +function CloseButton(props: { close: () => void }): JSX.Element { + return ( + + ) +} +function SizeToggle(props: SizeToggleProps): JSX.Element { + return ( + + ) +} diff --git a/packages/web/components/templates/article/PdfArticleContainer.tsx b/packages/web/components/templates/article/PdfArticleContainer.tsx index 80e0c3aec..f9cde6cb7 100644 --- a/packages/web/components/templates/article/PdfArticleContainer.tsx +++ b/packages/web/components/templates/article/PdfArticleContainer.tsx @@ -237,7 +237,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,6 +493,7 @@ export default function PdfArticleContainer( {props.showHighlightsModal && ( props.setShowHighlightsModal(false)} /* eslint-disable @typescript-eslint/no-empty-function */ diff --git a/packages/web/components/templates/homeFeed/HighlightItem.tsx b/packages/web/components/templates/homeFeed/HighlightItem.tsx index 43fdc60ed..def16c5e0 100644 --- a/packages/web/components/templates/homeFeed/HighlightItem.tsx +++ b/packages/web/components/templates/homeFeed/HighlightItem.tsx @@ -117,7 +117,7 @@ export function HighlightItem(props: HighlightItemProps): JSX.Element { {props.highlight.annotation ? props.highlight.annotation - : 'Add your notes...'} + : 'Add notes to this highlight...'} )} {isEditing && ( @@ -263,9 +263,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 } diff --git a/packages/web/lib/dateFormatting.ts b/packages/web/lib/dateFormatting.ts index 0ac9bff52..64e91d917 100644 --- a/packages/web/lib/dateFormatting.ts +++ b/packages/web/lib/dateFormatting.ts @@ -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)) +} diff --git a/packages/web/lib/networking/fragments/highlightFragment.ts b/packages/web/lib/networking/fragments/highlightFragment.ts index 94fdfd341..fe8634916 100644 --- a/packages/web/lib/networking/fragments/highlightFragment.ts +++ b/packages/web/lib/networking/fragments/highlightFragment.ts @@ -4,6 +4,7 @@ import { Label } from './labelFragment' export const highlightFragment = gql` fragment HighlightFields on Highlight { id + type shortId quote prefix @@ -24,9 +25,11 @@ export const highlightFragment = gql` } } ` +export type HighlightType = 'HIGHLIGHT' | 'REDACTION' | 'NOTE' export type Highlight = { id: string + type: HighlightType shortId: string quote: string prefix?: string diff --git a/packages/web/lib/networking/mutations/createHighlightMutation.ts b/packages/web/lib/networking/mutations/createHighlightMutation.ts index acfc18206..054a8b845 100644 --- a/packages/web/lib/networking/mutations/createHighlightMutation.ts +++ b/packages/web/lib/networking/mutations/createHighlightMutation.ts @@ -1,17 +1,27 @@ 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 + annotation?: string + + patch?: string + highlightPositionPercent?: number highlightPositionAnchorIndex?: number + + type?: HighlightType } type CreateHighlightOutput = { diff --git a/packages/web/package.json b/packages/web/package.json index d56609176..0a6b9e7be 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -50,6 +50,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", diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index 73ccd48bd..dec8d6c90 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -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) { diff --git a/yarn.lock b/yarn.lock index 7a98e6d10..1907ecdfe 100644 --- a/yarn.lock +++ b/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" @@ -16170,6 +16209,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 +17293,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 +18625,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" @@ -19504,6 +19558,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 +19599,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 +19746,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 +20425,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 +22562,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 +23516,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 +23935,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 +24124,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 +24498,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 +25165,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 +25727,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 +26366,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 +26401,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 +26822,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 +26876,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 +26926,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 +26941,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 +26958,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 +27243,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 +27340,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 +27358,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" From 1fb3c44b9311004c1c6de9962363c07cce5fd3c4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 23 Mar 2023 11:22:51 +0800 Subject: [PATCH 02/35] WIP: Use a reducer when creating notes --- .../templates/article/NotebookModal.tsx | 115 +++++++++++++----- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index b825235f2..96d15f338 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -12,9 +12,11 @@ import type { Highlight } from '../../../lib/networking/fragments/highlightFragm import { HighlightView } from '../../patterns/HighlightView' import { ChangeEvent, + Dispatch, useCallback, useEffect, useMemo, + useReducer, useRef, useState, } from 'react' @@ -39,6 +41,7 @@ import { v4 as uuidv4 } from 'uuid' import { nanoid } from 'nanoid' import throttle from 'lodash/throttle' import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' +import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' const mdParser = new MarkdownIt() @@ -51,6 +54,15 @@ type NotebookModalProps = { onOpenChange: (open: boolean) => void } +type HighlightListReducerAction = { + type: string + itemId?: string + createId?: string + removeId?: string + highlight?: Highlight + highlights?: Highlight[] +} + export const getHighlightLocation = (patch: string): number | undefined => { const dmp = new diff_match_patch() const patches = dmp.patch_fromText(patch) @@ -68,21 +80,60 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { const [notesEditMode, setNotesEditMode] = useState(true) const [, updateState] = useState({}) + const listReducer = ( + state: Highlight[], + action: HighlightListReducerAction + ) => { + switch (action.type) { + case 'RESET': + return action.highlights ?? [] + case 'CREATE_NOTE': + if (!action.highlight) { + throw new Error('Unable to create note') + } + return [...(action.highlights ?? []), action.highlight] + case 'UPDATE_NOTE': + return action.highlights ?? [] + case 'REMOVE_HIGHLIGHT': + // const item = state.find((li) => li.node.id === action.itemId) + // if (item && item.node.highlights) { + // item.node.highlights = item.node.highlights.filter( + // (h) => h.id !== action.highlightId + // ) + // } + // const result = state.filter( + // (item) => item.node.highlights && item.node.highlights.length > 0 + // ) + // return result + default: + throw new Error() + } + } + + const [highlights, dispatchList] = useReducer(listReducer, []) + + useEffect(() => { + dispatchList({ + type: 'RESET', + highlights: props.highlights, + }) + }, [props.highlights]) + const exportHighlights = useCallback(() => { ;(async () => { - if (!props.highlights) { + if (!highlights) { showErrorToast('No highlights to export') return } - const markdown = highlightsAsMarkdown(props.highlights) + const markdown = highlightsAsMarkdown(highlights) await navigator.clipboard.writeText(markdown) showSuccessToast('Highlight copied') })() - }, [props.highlights]) + }, [highlights]) const deleteDocumentNote = useCallback(() => { ;(async () => { - const notes = props.highlights.filter((h) => h.type == 'NOTE') + const notes = highlights.filter((h) => h.type == 'NOTE') notes.forEach(async (n) => { try { @@ -110,7 +161,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { return 0 } - return props.highlights + return highlights .filter((h) => h.type === undefined || h.type === 'HIGHLIGHT') .sort((a: Highlight, b: Highlight) => { if (a.highlightPositionPercent && b.highlightPositionPercent) { @@ -127,7 +178,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { } catch {} return a.createdAt.localeCompare(b.createdAt) }) - }, [props.highlights]) + }, [highlights]) return ( @@ -188,13 +239,14 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { /> h.type == 'NOTE')} + highlight={highlights.find((h) => h.type == 'NOTE')} sizeMode={sizeMode} mode={notesEditMode ? 'edit' : 'read'} setEditMode={setNotesEditMode} + dispatchList={dispatchList} /> - {/* {props.highlights.map((highlight) => ( + {props.highlights.map((highlight) => ( {highlight.annotation} - - Post Highlight - - - - - - - - ) -} diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 96d15f338..c07242a26 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -42,6 +42,7 @@ import { nanoid } from 'nanoid' import throttle from 'lodash/throttle' import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { HighlightNoteBox } from '../HighlightNoteBox' const mdParser = new MarkdownIt() @@ -77,7 +78,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { ) const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal') const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) - const [notesEditMode, setNotesEditMode] = useState(true) + const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>('edit') const [, updateState] = useState({}) const listReducer = ( @@ -190,63 +191,72 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { }} css={{ overflow: 'auto', - px: '20px', height: sizeMode === 'normal' ? 'unset' : '100%', maxWidth: sizeMode === 'normal' ? '640px' : '100%', + minHeight: '525px', }} > - + + + Notebook + - - Notebook - - - - }> - { - exportHighlights() - }} - title="Export Notebook" - /> - { - setShowConfirmDeleteNote(true) - }} - title="Delete Document Note" - /> - - props.onOpenChange(false)} /> - + + }> + { + exportHighlights() + }} + title="Export Notebook" + /> + { + setShowConfirmDeleteNote(true) + }} + title="Delete Document Note" + /> + + props.onOpenChange(false)} /> + + setNotesEditMode(edit ? 'edit' : 'preview')} /> - h.type == 'NOTE')} sizeMode={sizeMode} - mode={notesEditMode ? 'edit' : 'read'} + mode={notesEditMode} setEditMode={setNotesEditMode} - dispatchList={dispatchList} + // dispatchList={dispatchList} /> - {props.highlights.map((highlight) => ( + {/* {props.highlights.map((highlight) => ( {highlight.annotation} + + )} ) : ( diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 6fea3acc7..385da5ffd 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -1,3 +1,4 @@ +import { BookOpen, PencilLine } from 'phosphor-react' import { Fragment, useMemo, useState } from 'react' import type { Highlight } from '../../lib/networking/fragments/highlightFragment' import { LabelChip } from '../elements/LabelChip' @@ -63,7 +64,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { }} /> - + { if (props.scrollToHighlight) { @@ -95,15 +96,43 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { ))} - + + + { + setNoteMode(noteMode == 'preview' ? 'edit' : 'preview') + event.preventDefault() + }} + > + {noteMode === 'edit' ? ( + + ) : ( + + )} + + ) diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 6cc35eed1..5a5d1ef92 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -29,6 +29,7 @@ import { nanoid } from 'nanoid' import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' import { HighlightNoteBox } from '../../patterns/HighlightNotes' import { HighlightViewItem } from './HighlightViewItem' +import { Notebook } from './Notebook' type NotebookModalProps = { pageId: string @@ -351,196 +352,12 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { - - setNotesEditMode(edit ? 'edit' : 'preview')} - /> - - - {/* {annotations.allAnnotations.map((highlight) => ( - - {highlight.annotation} - - - ))} */} - - - - {sortedHighlights.map((highlight) => ( - { - dispatchAnnotations({ - type: 'DELETE_HIGHLIGHT', - deleteHighlightId: highlight.id, - }) - }} - updateHighlight={() => { - dispatchAnnotations({ - type: 'UPDATE_HIGHLIGHT', - updateHighlight: highlight, - }) - }} - /> - ))} - {sortedHighlights.length === 0 && ( - - You have not added any highlights to this document. - - )} - - + - {showConfirmDeleteHighlightId && ( - { - ;(async () => { - const success = await deleteHighlightMutation( - showConfirmDeleteHighlightId - ) - if (success) { - dispatchAnnotations({ - type: 'DELETE_HIGHLIGHT', - deleteHighlightId: showConfirmDeleteHighlightId, - }) - showSuccessToast('Highlight deleted.') - } else { - showErrorToast('Error deleting highlight') - } - })() - setShowConfirmDeleteHighlightId(undefined) - }} - onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)} - icon={ - - } - /> - )} - {labelsTarget && ( - { - const result = setLabelsForHighlight( - labelsTarget.id, - labels.map((label) => label.id) - ) - return result - }} - /> - )} - {showConfirmDeleteNote && ( - { - deleteDocumentNote() - setShowConfirmDeleteNote(false) - }} - onOpenChange={() => setShowConfirmDeleteNote(false)} - /> - )} ) } -type TitledSectionProps = { - title: string - editMode?: boolean - setEditMode?: (set: boolean) => void -} - -function TitledSection(props: TitledSectionProps): JSX.Element { - return ( - <> - - - {props.title} - - {props.setEditMode && ( - { - if (props.setEditMode) { - props.setEditMode(!props.editMode) - } - event.preventDefault() - }} - > - {props.editMode ? ( - - ) : ( - - )} - - )} - - - - ) -} - type SizeToggleProps = { mode: 'normal' | 'maximized' setMode: (mode: 'normal' | 'maximized') => void diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 99a9f7ea1..6e95867c9 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -21,6 +21,7 @@ import { } from '../../patterns/LibraryCards/LibraryCardStyles' import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard' import { HighlightViewItem } from '../article/HighlightViewItem' +import { Notebook } from '../article/Notebook' import { EmptyHighlights } from './EmptyHighlights' import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from './HeaderSpacer' import { highlightsAsMarkdown } from './HighlightItem' @@ -188,28 +189,19 @@ export function HighlightItemsLayout( height: '100%', width: '100%', flexGrow: '1', + justifyContent: 'center', + overflowY: 'scroll', '@lgDown': { display: 'none', flexGrow: 'unset', }, }} > - - - + )} @@ -369,9 +361,6 @@ type HighlightListProps = { } function HighlightList(props: HighlightListProps): JSX.Element { - const [notesEditMode, setNotesEditMode] = useState<'preview' | 'edit'>( - 'preview' - ) const exportHighlights = useCallback(() => { ;(async () => { if (!props.item.node.highlights) { @@ -384,149 +373,201 @@ function HighlightList(props: HighlightListProps): JSX.Element { })() }, [props.item.node.highlights]) - const note = useMemo(() => { - const note = (props.item.node.highlights ?? []).find( - (h) => h.type === 'NOTE' - ) - console.log('NOTE: ', note) - return note - }, [props.item]) - - const sortedHighlights = useMemo(() => { - return (props.item.node.highlights ?? []).filter( - (h) => h.type === 'HIGHLIGHT' - ) - }, [props.item]) - return ( - - - - + }> + { + exportHighlights() }} - > - NOTEBOOK - - }> - { - exportHighlights() - }} - title="Export" - /> - - - - - - NOTE - - - { - console.log('saving text', highlight) + title="Export" + /> + + + + { + console.log('closing notebook: ', highlights, deletedAnnotations) }} /> - - - {sortedHighlights && ( - <> - - - HIGHLIGHTS - - - - {sortedHighlights.map((highlight) => ( - <> - { - console.log('updated highlight: ', highlight) - }} - /> - - - ))} - - - - - )} - - + + ) + + // return ( + // + // + // + // + // NOTEBOOK + // + // }> + // { + // exportHighlights() + // }} + // title="Export" + // /> + // + // + + // + // + // NOTE + // + // + // { + // console.log('saving text', highlight) + // }} + // /> + // + + // {sortedHighlights && ( + // <> + // + // + // HIGHLIGHTS + // + // + // + // {sortedHighlights.map((highlight) => ( + // <> + // { + // console.log('updated highlight: ', highlight) + // }} + + // deleteHighlightAction={(highlight) => { + // console.log('deleting: ', highlight) + // }} + + // setSetLabelsTarget: (highlight: Highlight) => void + // setShowConfirmDeleteHighlightId: (id: string | undefined) => void + + // /> + // + // + // ))} + // + // + // + // + // )} + // + // + // ) } type HighlightCountChipProps = { From 2a0244b612faad556fc8c83016ede0a1ccd8c0fd Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 27 Mar 2023 14:53:33 +0800 Subject: [PATCH 15/35] Refactor for PDF highlights --- .../templates/article/NotebookModal.tsx | 244 ++---------------- .../templates/article/PdfArticleContainer.tsx | 8 + .../templates/homeFeed/HighlightsLayout.tsx | 7 +- 3 files changed, 28 insertions(+), 231 deletions(-) diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 5a5d1ef92..641e6242a 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -55,244 +55,38 @@ type AnnotationInfo = { } export function NotebookModal(props: NotebookModalProps): JSX.Element { - const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = - useState(undefined) - const [labelsTarget, setLabelsTarget] = useState( - undefined - ) const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal') const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) - const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>( - 'preview' + const [allAnnotations, setAllAnnotations] = useState( + undefined ) + const [deletedAnnotations, setDeletedAnnotations] = useState< + Highlight[] | undefined + >(undefined) - const [, updateState] = useState({}) + const handleClose = useCallback(() => { + props.onClose(allAnnotations ?? [], deletedAnnotations ?? []) + }, [allAnnotations, deletedAnnotations]) - const annotationsReducer = ( - state: AnnotationInfo, - action: { - type: string - allHighlights?: Highlight[] - note?: Highlight | undefined - - updateHighlight?: Highlight | undefined - deleteHighlightId?: string | undefined - } - ) => { - switch (action.type) { - case 'RESET': { - console.log(' -- reseting highlights: ', action.allHighlights) - 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 - 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]) + const handleAnnotationsChange = useCallback( + (allAnnotations, deletedAnnotations) => { + setAllAnnotations(allAnnotations) + setDeletedAnnotations(deletedAnnotations) + }, + [] + ) const exportHighlights = useCallback(() => { ;(async () => { - if (!annotations) { + if (!allAnnotations) { showErrorToast('No highlights to export') return } - const markdown = highlightsAsMarkdown(annotations.allAnnotations) + const markdown = highlightsAsMarkdown(allAnnotations) await navigator.clipboard.writeText(markdown) showSuccessToast('Highlight copied') })() - }, [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.pageId, - 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.pageId] - ) - - const handleClose = useCallback(() => { - props.onClose(annotations.allAnnotations, annotations.deletedAnnotations) - }, [annotations]) + }, [allAnnotations]) return ( @@ -352,7 +146,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { - + ) diff --git a/packages/web/components/templates/article/PdfArticleContainer.tsx b/packages/web/components/templates/article/PdfArticleContainer.tsx index 93730bc34..256220c97 100644 --- a/packages/web/components/templates/article/PdfArticleContainer.tsx +++ b/packages/web/components/templates/article/PdfArticleContainer.tsx @@ -424,6 +424,7 @@ export default function PdfArticleContainer( document.addEventListener('deleteHighlightbyId', async (event) => { const annotationId = (event as CustomEvent).detail as string + console.log(' DELETING ANNOTATION BY ID: ', annotationId) for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) { const annotations = await instance.getAnnotations(pageIdx) for (let annIdx = 0; annIdx < annotations.size; annIdx++) { @@ -432,6 +433,7 @@ export default function PdfArticleContainer( continue } const storedId = annotationOmnivoreId(annotation) + console.log(' --- storedId:', storedId) if (storedId == annotationId) { await instance.delete(annotation) await deleteHighlightMutation(annotationId) @@ -501,6 +503,12 @@ export default function PdfArticleContainer( updatedHighlights, deletedAnnotations ) + deletedAnnotations.forEach((highlight) => { + const event = new CustomEvent('deleteHighlightbyId', { + detail: highlight.id, + }) + document.dispatchEvent(event) + }) props.setShowHighlightsModal(false) }} /> diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 6e95867c9..5f727a517 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -403,6 +403,7 @@ function HighlightList(props: HighlightListProps): JSX.Element { width: '100%', color: 'thTextContrast2', m: '0px', + pb: '5px', }} > NOTEBOOK @@ -420,12 +421,6 @@ function HighlightList(props: HighlightListProps): JSX.Element { { - console.log('closing notebook: ', highlights, deletedAnnotations) - }} /> From 495b509c51224b44e85aef7d243731321a4f3506 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 27 Mar 2023 15:04:20 +0800 Subject: [PATCH 16/35] Update notebook modals, allow edit mode --- .../templates/article/HighlightViewItem.tsx | 54 ++ .../components/templates/article/Notebook.tsx | 476 ++++++++++++++++++ .../templates/article/NotebookModal.tsx | 6 +- 3 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 packages/web/components/templates/article/HighlightViewItem.tsx create mode 100644 packages/web/components/templates/article/Notebook.tsx diff --git a/packages/web/components/templates/article/HighlightViewItem.tsx b/packages/web/components/templates/article/HighlightViewItem.tsx new file mode 100644 index 000000000..7be05accb --- /dev/null +++ b/packages/web/components/templates/article/HighlightViewItem.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react' +import { Highlight } from '../../../lib/networking/fragments/highlightFragment' +import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { HighlightView } from '../../patterns/HighlightView' +import { HighlightsMenu } from '../homeFeed/HighlightItem' + +type HighlightViewItemProps = { + highlight: Highlight + scrollToHighlight?: (arg: 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 ( + setHover(true)} + onMouseLeave={() => setHover(false)} + > + + + + + + + + + ) +} diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx new file mode 100644 index 000000000..746c0cfcf --- /dev/null +++ b/packages/web/components/templates/article/Notebook.tsx @@ -0,0 +1,476 @@ +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 } from '../../patterns/HighlightNotes' +import { HighlightViewItem } from './HighlightViewItem' +import { ConfirmationModal } from '../../patterns/ConfirmationModal' +import { TrashIcon } from '../../elements/images/TrashIcon' + +type NotebookProps = { + pageId: string + highlights: Highlight[] + scrollToHighlight?: (arg: string) => void + + sizeMode: 'normal' | 'maximized' + + 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) + const [labelsTarget, setLabelsTarget] = useState( + 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.pageId, + 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.pageId] + ) + return ( + + setNotesEditMode(edit ? 'edit' : 'preview')} + /> + + + {/* {annotations.allAnnotations.map((highlight) => ( + + {highlight.annotation} + + + ))} */} + + + + {sortedHighlights.map((highlight) => ( + { + dispatchAnnotations({ + type: 'DELETE_HIGHLIGHT', + deleteHighlightId: highlight.id, + }) + }} + updateHighlight={() => { + dispatchAnnotations({ + type: 'UPDATE_HIGHLIGHT', + updateHighlight: highlight, + }) + }} + /> + ))} + {sortedHighlights.length === 0 && ( + + You have not added any highlights to this document. + + )} + + {showConfirmDeleteHighlightId && ( + { + ;(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={ + + } + /> + )} + {labelsTarget && ( + { + const result = setLabelsForHighlight( + labelsTarget.id, + labels.map((label) => label.id) + ) + return result + }} + /> + )} + {showConfirmDeleteNote && ( + { + deleteDocumentNote() + setShowConfirmDeleteNote(false) + }} + onOpenChange={() => setShowConfirmDeleteNote(false)} + /> + )} + + ) +} + +type TitledSectionProps = { + title: string + editMode?: boolean + setEditMode?: (set: boolean) => void +} + +function TitledSection(props: TitledSectionProps): JSX.Element { + return ( + <> + + + {props.title} + + {props.setEditMode && ( + { + if (props.setEditMode) { + props.setEditMode(!props.editMode) + } + event.preventDefault() + }} + > + {props.editMode ? ( + + ) : ( + + )} + + )} + + + ) +} diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 641e6242a..540a8fcec 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -146,7 +146,11 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { - + ) From 95d453298c8f950f7b6dd55144962f95d9604667 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 27 Mar 2023 15:07:19 +0800 Subject: [PATCH 17/35] Unused imports --- .../web/components/templates/homeFeed/HighlightsLayout.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 5f727a517..f0033da52 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -13,14 +13,11 @@ import { Dropdown, DropdownOption } from '../../elements/DropdownElements' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { MenuTrigger } from '../../elements/MenuTrigger' import { StyledText } from '../../elements/StyledText' -import { HighlightNoteBox } from '../../patterns/HighlightNotes' -import { HighlightView } from '../../patterns/HighlightView' import { MetaStyle, timeAgo, } from '../../patterns/LibraryCards/LibraryCardStyles' import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard' -import { HighlightViewItem } from '../article/HighlightViewItem' import { Notebook } from '../article/Notebook' import { EmptyHighlights } from './EmptyHighlights' import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from './HeaderSpacer' From 1acfb6dcc46ba18c64d4c02ff259023f4e90163e Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 09:57:41 +0800 Subject: [PATCH 18/35] Add missing attribute --- packages/web/components/templates/homeFeed/HighlightsLayout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index f0033da52..00004a934 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -416,6 +416,7 @@ function HighlightList(props: HighlightListProps): JSX.Element { From e3675295f0c29ec9d34347bf0a39f0a25ddde523 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 12:26:03 +0800 Subject: [PATCH 19/35] WIP: use MD for highlight quotes --- .../web/components/patterns/HighlightView.tsx | 6 +++-- .../web/lib/highlights/createHighlight.ts | 25 ++++++++++++++++++- packages/web/package.json | 1 + 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 385da5ffd..ad75389e8 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -11,6 +11,7 @@ import { } from '../elements/LayoutPrimitives' import { styled } from '../tokens/stitches.config' import { HighlightViewNote } from './HighlightNotes' +import ReactMarkdown from 'react-markdown' type HighlightViewProps = { highlight: Highlight @@ -72,7 +73,8 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { } }} > - + {/* ))} - + */} {props.highlight.labels?.map(({ name, color }, index) => ( diff --git a/packages/web/lib/highlights/createHighlight.ts b/packages/web/lib/highlights/createHighlight.ts index 045f28819..289290cf2 100644 --- a/packages/web/lib/highlights/createHighlight.ts +++ b/packages/web/lib/highlights/createHighlight.ts @@ -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,14 @@ export async function createHighlight( extendRangeToWordBoundaries(range) + // selection: Selection + var container = document.createElement('div') + // for (var i = 0, len = input.selection.selection.rangeCount; i < len; ++i) { + container.appendChild(range.cloneContents()) + // } + console.log('container HTML: ', container.innerHTML) + console.log('container markdown: ', htmlToMarkdown(container.innerHTML)) + const id = uuidv4() const patch = generateDiffPatch(range) @@ -81,7 +104,7 @@ export async function createHighlight( const newHighlightAttributes = { prefix: highlightAttributes.prefix, suffix: highlightAttributes.suffix, - quote: highlightAttributes.quote, + quote: htmlToMarkdown(container.innerHTML), id, shortId: nanoid(8), patch, diff --git a/packages/web/package.json b/packages/web/package.json index b055577e7..119b1298e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -44,6 +44,7 @@ "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", From d77ce3c46d49a9facaf50e1165226edd00227ae2 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 13:23:01 +0800 Subject: [PATCH 20/35] Improve CSS on MD boxes --- .../web/components/patterns/HighlightView.tsx | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index ad75389e8..efe45e0a9 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -25,17 +25,11 @@ const StyledQuote = styled(Blockquote, { margin: '0px 0px 0px 0px', fontSize: '18px', lineHeight: '27px', - color: '$grayText', }) export function HighlightView(props: HighlightViewProps): JSX.Element { const [noteMode, setNoteMode] = useState<'preview' | 'edit'>('preview') - const lines = useMemo( - () => (props.highlight.quote || '').split('\n'), - [props.highlight.quote] - ) - return ( - + { if (props.scrollToHighlight) { @@ -73,25 +73,22 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { } }} > - - {/* *': { + m: '0px', + }, + color: '$grayText', + img: { + display: 'block', + margin: '0.5em auto !important', + maxWidth: '100% !important', + height: 'auto', + }, }} > - {lines.map((line: string, index: number) => ( - - {line} - {index !== lines.length - 1 && ( - <> -
-
- - )} -
- ))} -
*/} + +
{props.highlight.labels?.map(({ name, color }, index) => ( From a55eab32f32d7596057005e8c7e2bda9c501f639 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 14:04:42 +0800 Subject: [PATCH 21/35] Send HTML with highlight requests --- packages/web/lib/highlights/createHighlight.ts | 17 ++++++++--------- .../mutations/createHighlightMutation.ts | 1 + .../mutations/mergeHighlightMutation.ts | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/web/lib/highlights/createHighlight.ts b/packages/web/lib/highlights/createHighlight.ts index 289290cf2..2a7b8715c 100644 --- a/packages/web/lib/highlights/createHighlight.ts +++ b/packages/web/lib/highlights/createHighlight.ts @@ -57,13 +57,9 @@ export async function createHighlight( extendRangeToWordBoundaries(range) - // selection: Selection - var container = document.createElement('div') - // for (var i = 0, len = input.selection.selection.rangeCount; i < len; ++i) { + // Create a temp container for copying the range HTML + const container = document.createElement('div') container.appendChild(range.cloneContents()) - // } - console.log('container HTML: ', container.innerHTML) - console.log('container markdown: ', htmlToMarkdown(container.innerHTML)) const id = uuidv4() const patch = generateDiffPatch(range) @@ -102,12 +98,15 @@ export async function createHighlight( ) const newHighlightAttributes = { - prefix: highlightAttributes.prefix, - suffix: highlightAttributes.suffix, - quote: htmlToMarkdown(container.innerHTML), 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, diff --git a/packages/web/lib/networking/mutations/createHighlightMutation.ts b/packages/web/lib/networking/mutations/createHighlightMutation.ts index 054a8b845..2193b46a1 100644 --- a/packages/web/lib/networking/mutations/createHighlightMutation.ts +++ b/packages/web/lib/networking/mutations/createHighlightMutation.ts @@ -14,6 +14,7 @@ export type CreateHighlightInput = { prefix?: string suffix?: string quote?: string + html?: string annotation?: string patch?: string diff --git a/packages/web/lib/networking/mutations/mergeHighlightMutation.ts b/packages/web/lib/networking/mutations/mergeHighlightMutation.ts index fe63a6fc1..11b05d434 100644 --- a/packages/web/lib/networking/mutations/mergeHighlightMutation.ts +++ b/packages/web/lib/networking/mutations/mergeHighlightMutation.ts @@ -10,6 +10,7 @@ export type MergeHighlightInput = { quote: string prefix?: string suffix?: string + html?: string annotation?: string overlapHighlightIdList: string[] highlightPositionPercent?: number From 2607445a0b09442e846be958638207fc630487c5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 14:36:34 +0800 Subject: [PATCH 22/35] Wider views on small devices for notebooks --- .../components/templates/article/Notebook.tsx | 20 ++++++--- .../templates/article/NotebookModal.tsx | 41 +++++++------------ 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx index 746c0cfcf..dc3f401a1 100644 --- a/packages/web/components/templates/article/Notebook.tsx +++ b/packages/web/components/templates/article/Notebook.tsx @@ -53,13 +53,11 @@ type AnnotationInfo = { export function Notebook(props: NotebookProps): JSX.Element { const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = useState(undefined) - const [labelsTarget, setLabelsTarget] = useState( - undefined - ) + const [labelsTarget, setLabelsTarget] = + useState(undefined) const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) - const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>( - 'preview' - ) + const [notesEditMode, setNotesEditMode] = + useState<'edit' | 'preview'>('preview') const [, updateState] = useState({}) const annotationsReducer = ( @@ -351,7 +349,17 @@ export function Notebook(props: NotebookProps): JSX.Element { You have not added any highlights to this document. )} + + {showConfirmDeleteHighlightId && ( ('normal') const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) - const [allAnnotations, setAllAnnotations] = useState( - undefined - ) - const [deletedAnnotations, setDeletedAnnotations] = useState< - Highlight[] | undefined - >(undefined) + const [allAnnotations, setAllAnnotations] = + useState(undefined) + const [deletedAnnotations, setDeletedAnnotations] = + useState(undefined) const handleClose = useCallback(() => { props.onClose(allAnnotations ?? [], deletedAnnotations ?? []) @@ -100,6 +85,13 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { 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%)', + }, }} > void }): JSX.Element { '&:hover': { bg: '#898989', }, - '@mdDown': { - display: 'none', - }, }} onClick={(event) => { props.close() From d6a3eaea92bb00d06c9629319c08ca1d77a16ce0 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 14:48:31 +0800 Subject: [PATCH 23/35] Fix notebook layout to match design, remove unused file --- .../elements/HighlightNoteTextEditArea.tsx | 91 ------------------- .../components/patterns/HighlightNotes.tsx | 10 +- .../web/components/patterns/HighlightView.tsx | 6 +- .../templates/article/NotebookModal.tsx | 4 +- 4 files changed, 12 insertions(+), 99 deletions(-) delete mode 100644 packages/web/components/elements/HighlightNoteTextEditArea.tsx diff --git a/packages/web/components/elements/HighlightNoteTextEditArea.tsx b/packages/web/components/elements/HighlightNoteTextEditArea.tsx deleted file mode 100644 index 4fad25dc7..000000000 --- a/packages/web/components/elements/HighlightNoteTextEditArea.tsx +++ /dev/null @@ -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): void => { - setNoteContent(event.target.value) - }, - [setNoteContent] - ) - - return ( - - - - - - - - ) -} diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index be51b17b8..7e223d26d 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -286,12 +286,14 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { <> *': { m: '0px', }, diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index efe45e0a9..c421bbb24 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -41,7 +41,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { @@ -78,6 +78,8 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { '> *': { m: '0px', }, + fontSize: '15px', + lineHeight: 1.5, color: '$grayText', img: { display: 'block', diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 9a07f26dd..1bc5ce552 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -3,7 +3,7 @@ import { ModalOverlay, ModalContent, } from '../../elements/ModalPrimitives' -import { Box, HStack } from '../../elements/LayoutPrimitives' +import { HStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' @@ -114,7 +114,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { css={{ ml: 'auto', cursor: 'pointer', - gap: '5px', + gap: '15px', mr: '-5px', }} distribution="center" From 86c3e0835558ee733e67f89d220a178dc09a47cf Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 14:54:22 +0800 Subject: [PATCH 24/35] Update color, remove unused code --- packages/web/components/patterns/HighlightNotes.tsx | 4 ++-- .../web/components/templates/article/Notebook.tsx | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index 7e223d26d..c307a74ee 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -291,8 +291,8 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { width: '100%', marginTop: '15px', color: '#898989', - fontSize: '11px', - border: '1px solid #D9D9D9', + fontSize: '13px', + border: '1px solid $thBorderColor', '> *': { m: '0px', diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx index dc3f401a1..e00fe9b60 100644 --- a/packages/web/components/templates/article/Notebook.tsx +++ b/packages/web/components/templates/article/Notebook.tsx @@ -298,18 +298,6 @@ export function Notebook(props: NotebookProps): JSX.Element { saveText={handleSaveNoteText} /> - {/* {annotations.allAnnotations.map((highlight) => ( - - {highlight.annotation} - - - ))} */} From 579bc45237d5f1efbba04f45ac94420d13739822 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 15:44:07 +0800 Subject: [PATCH 25/35] Formatting on highlight notes --- .../components/patterns/HighlightNotes.tsx | 27 +++++++++++++++---- .../web/components/patterns/HighlightView.tsx | 4 +-- .../web/components/tokens/stitches.config.ts | 2 ++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index c307a74ee..69376f0d9 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -55,6 +55,7 @@ export function HighlightNoteBox(props: NoteSectionProps): JSX.Element { text={props.text} saveText={saveText} lastSaved={lastSaved} + fillBackground={false} /> ) } @@ -101,6 +102,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { text={props.text} saveText={saveText} lastSaved={lastSaved} + fillBackground={true} /> ) } @@ -113,6 +115,7 @@ type MarkdownNote = { setEditMode: (set: 'edit' | 'preview') => void text: string | undefined + fillBackground: boolean | undefined lastSaved: Date | undefined saveText: (text: string, updateTime: Date) => void @@ -286,14 +289,28 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { <> *': { m: '0px', }, diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index c421bbb24..510a7df3d 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -98,7 +98,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { ))} @@ -115,7 +115,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { css={{ lineHeight: '1', marginLeft: '20px', - marginTop: '20px', + marginTop: '15px', cursor: 'pointer', borderRadius: '1000px', '&:hover': { diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index 95c696e04..74c378191 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -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', From 3fdc1994017f0375abc42f4d4a1a7cc7f879d298 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 15:50:19 +0800 Subject: [PATCH 26/35] Padding on empty note boxes --- packages/web/components/patterns/HighlightNotes.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index 69376f0d9..eb2c0a2fb 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -298,13 +298,13 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { paddingLeft: props.fillBackground && props.text ? '10px' - : props.fillBackground + : !props.text ? '5px' : '0px', paddingRight: props.fillBackground && props.text ? '10px' - : props.fillBackground + : !props.text ? '5px' : '0px', color: props.text ? '$thHighContrast' : '#898989', From ddb32a77041026f68586f6e1ab747cb3c16296e8 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 15:57:17 +0800 Subject: [PATCH 27/35] Allow passing children as text --- packages/web/components/patterns/HighlightView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 510a7df3d..89000c99d 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -1,5 +1,6 @@ +/* eslint-disable react/no-children-prop */ import { BookOpen, PencilLine } from 'phosphor-react' -import { Fragment, useMemo, useState } from 'react' +import { useState } from 'react' import type { Highlight } from '../../lib/networking/fragments/highlightFragment' import { LabelChip } from '../elements/LabelChip' import { From a6bda62a914a4e43bac9bdf97a858d9eec6a69a4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 19:43:03 +0800 Subject: [PATCH 28/35] Min font size on iOS inputs should be 16px or we will zoom on input --- packages/web/components/patterns/HighlightNotes.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index eb2c0a2fb..c9393ad1a 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -178,6 +178,7 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { '.rc-md-editor .editor-container .sec-md .input': { padding: '10px', borderRadius: '5px', + fontSize: '16px', }, }} onKeyDown={(event: React.KeyboardEvent) => { From 2d4723b2cda764543c5b2d7b4aeecc89b0229446 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 21:04:18 +0800 Subject: [PATCH 29/35] Add extra spacing at bottom of mobile highlight list --- .../web/components/templates/homeFeed/HighlightsLayout.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 00004a934..920ed9422 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -33,9 +33,8 @@ type HighlightItemsLayoutProps = { export function HighlightItemsLayout( props: HighlightItemsLayoutProps ): JSX.Element { - const [currentItem, setCurrentItem] = useState( - undefined - ) + const [currentItem, setCurrentItem] = + useState(undefined) const listReducer = ( state: LibraryItem[], @@ -248,6 +247,7 @@ function LibraryItemsList(props: LibraryItemsListProps): JSX.Element { )}
))} + ) } From bd705165c6e6fd06c81213649e4a7c9d0b962fa4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 21:47:13 +0800 Subject: [PATCH 30/35] Improve highlight list scrolling --- .../web/components/templates/homeFeed/HighlightsLayout.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 920ed9422..5acb20656 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -124,6 +124,9 @@ export function HighlightItemsLayout( '@xlgDown': { height: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`, }, + '@lgDown': { + overflowY: 'scroll', + }, bg: '$thBackground2', overflow: 'hidden', }} From f6fc9740406af4a05f6146e39a3353852c66b817 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 31 Mar 2023 21:49:26 +0800 Subject: [PATCH 31/35] Add a key to help with rerenders --- packages/web/components/patterns/HighlightNotes.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index c9393ad1a..febd0979e 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -189,6 +189,7 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { }} > Date: Mon, 3 Apr 2023 10:41:18 +0800 Subject: [PATCH 32/35] Revert scrolling changes --- .../patterns/LibraryCards/LibraryHighlightGridCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx index 8e68081f9..39613cbcb 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx @@ -127,7 +127,7 @@ export function LibraryHighlightGridCard( distribution="start" > {sortedHighlights.map((highlight) => ( - <> + - - + + ))} From 25530eb411ad5dfe1993d2da3199e33a53c2f8cd Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 3 Apr 2023 10:56:39 +0800 Subject: [PATCH 33/35] Enable the AddLinkModal in the Highlights Layout --- .../templates/homeFeed/HomeFeedContainer.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 70d39ba04..b1b8a2f4f 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -78,9 +78,8 @@ export function HomeFeedContainer(): JSX.Element { const gridContainerRef = useRef(null) - const [labelsTarget, setLabelsTarget] = useState( - undefined - ) + const [labelsTarget, setLabelsTarget] = + useState(undefined) const [showAddLinkModal, setShowAddLinkModal] = useState(false) const [showEditTitleModal, setShowEditTitleModal] = useState(false) @@ -701,6 +700,10 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { {...props} /> )} + + {props.showAddLinkModal && ( + props.setShowAddLinkModal(false)} /> + )}
) @@ -916,10 +919,6 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element { )} - - {props.showAddLinkModal && ( - props.setShowAddLinkModal(false)} /> - )} {props.showEditTitleModal && ( From f20cbed5633e553bed90bd8edc04655176e3d7fa Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 3 Apr 2023 13:46:19 +0800 Subject: [PATCH 34/35] Improve highlight scrolling from notebooks --- .../web/components/patterns/HighlightView.tsx | 9 +- .../templates/article/ArticleContainer.tsx | 21 ++- .../templates/article/HighlightViewItem.tsx | 16 +- .../templates/article/HighlightsLayer.tsx | 137 ++++++++++-------- .../components/templates/article/Notebook.tsx | 20 ++- .../templates/article/NotebookModal.tsx | 28 ++-- .../templates/article/PdfArticleContainer.tsx | 107 +++++++------- .../templates/homeFeed/HighlightItem.tsx | 59 +++++++- .../templates/homeFeed/HighlightsLayout.tsx | 49 ++++++- packages/web/lib/appConfig.ts | 17 --- .../queries/useGetLibraryItemsQuery.tsx | 6 + .../web/pages/[username]/[slug]/index.tsx | 4 +- .../web/pages/app/[username]/[slug]/index.tsx | 12 +- 13 files changed, 303 insertions(+), 182 deletions(-) diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 89000c99d..79e295bf9 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -18,7 +18,6 @@ type HighlightViewProps = { highlight: Highlight author?: string title?: string - scrollToHighlight?: (arg: string) => void updateHighlight: (highlight: Highlight) => void } @@ -67,13 +66,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { paddingLeft: '15px', }} > - { - if (props.scrollToHighlight) { - props.scrollToHighlight(props.highlight.id) - } - }} - > + *': { diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 174f74f4e..5f80488e9 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -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( - null - ) - const [fontFamilyOverride, setFontFamilyOverride] = useState( - null - ) + const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] = + useState(null) + const [lineHeightOverride, setLineHeightOverride] = + useState(null) + const [fontFamilyOverride, setFontFamilyOverride] = + useState(null) const [highContrastText, setHighContrastText] = useState( props.highContrastText ?? false ) @@ -388,13 +386,14 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element { void + + viewInReader: (highlightId: string) => void + deleteHighlightAction: () => void updateHighlight: (highlight: Highlight) => void @@ -26,7 +36,6 @@ export function HighlightViewItem(props: HighlightViewItemProps): JSX.Element { @@ -42,7 +51,10 @@ export function HighlightViewItem(props: HighlightViewItemProps): JSX.Element { }} > + setShowHighlightsModal: React.Dispatch> articleMutations: ArticleMutations } @@ -59,6 +66,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({ highlightModalAction: 'none' }) @@ -68,15 +76,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(undefined) const [selectionData, setSelectionData] = useSelection(highlightLocations) - const [labelsTarget, setLabelsTarget] = useState( - undefined - ) + const [labelsTarget, setLabelsTarget] = + useState(undefined) const canShareNative = useCanShareNative() @@ -141,7 +147,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]) @@ -181,23 +190,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) => { @@ -282,7 +288,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { } }, [ - handleNativeShare, highlights, openNoteModal, props.articleId, @@ -408,37 +413,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({ @@ -454,7 +459,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { [ createHighlightCallback, focusedHighlight, - handleNativeShare, openNoteModal, props.highlightBarDisabled, props.isAppleAppEmbed, @@ -667,9 +671,28 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { if (props.showHighlightsModal) { return ( { + // 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) + }} /> ) } diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx index e00fe9b60..1d852c8cf 100644 --- a/packages/web/components/templates/article/Notebook.tsx +++ b/packages/web/components/templates/article/Notebook.tsx @@ -20,14 +20,22 @@ import { HighlightNoteBox } 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 = { - pageId: string + viewer: UserBasicData + + item: ReadableItem highlights: Highlight[] - scrollToHighlight?: (arg: string) => void sizeMode: 'normal' | 'maximized' + viewInReader: (highlightId: string) => void + onAnnotationsChanged?: ( highlights: Highlight[], deletedAnnotations: Highlight[] @@ -242,7 +250,7 @@ export function Notebook(props: NotebookProps): JSX.Element { id: noteId, shortId: nanoid(8), type: 'NOTE', - articleId: props.pageId, + articleId: props.item.id, annotation: text, }) console.log('success creating annotation note: ', success) @@ -277,7 +285,7 @@ export function Notebook(props: NotebookProps): JSX.Element { return } }, - [annotations, props.pageId] + [annotations, props.item] ) return ( ( { diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 1bc5ce552..249b3001a 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -17,11 +17,16 @@ import { MenuTrigger } from '../../elements/MenuTrigger' 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' type NotebookModalProps = { - pageId: string + viewer: UserBasicData + + item: ReadableItem highlights: Highlight[] - scrollToHighlight?: (arg: string) => void + + viewHighlightInReader: (arg: string) => void onClose: (highlights: Highlight[], deletedAnnotations: Highlight[]) => void } @@ -31,16 +36,6 @@ export const getHighlightLocation = (patch: string): number | undefined => { return patches[0].start1 || undefined } -type AnnotationInfo = { - loaded: boolean - - note: Highlight | undefined - noteId: string - - allAnnotations: Highlight[] - deletedAnnotations: Highlight[] -} - export function NotebookModal(props: NotebookModalProps): JSX.Element { const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal') const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) @@ -73,6 +68,14 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { })() }, [allAnnotations]) + const viewInReader = useCallback( + (highlightId) => { + props.viewHighlightInReader(highlightId) + handleClose() + }, + [props, handleClose] + ) + return ( @@ -141,6 +144,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { diff --git a/packages/web/components/templates/article/PdfArticleContainer.tsx b/packages/web/components/templates/article/PdfArticleContainer.tsx index 256220c97..81c859fc1 100644 --- a/packages/web/components/templates/article/PdfArticleContainer.tsx +++ b/packages/web/components/templates/article/PdfArticleContainer.tsx @@ -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> @@ -30,43 +30,41 @@ export default function PdfArticleContainer( props: PdfArticleContainerProps ): JSX.Element { const containerRef = useRef(null) - const [shareTarget, setShareTarget] = useState( - undefined - ) + const [shareTarget, setShareTarget] = + useState(undefined) const [notebookKey, setNotebookKey] = useState(uuidv4()) const [noteTarget, setNoteTarget] = useState(undefined) - const [noteTargetPageIndex, setNoteTargetPageIndex] = useState< - number | undefined - >(undefined) + const [noteTargetPageIndex, setNoteTargetPageIndex] = + useState(undefined) const highlightsRef = useRef([]) 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] } @@ -424,7 +422,6 @@ export default function PdfArticleContainer( document.addEventListener('deleteHighlightbyId', async (event) => { const annotationId = (event as CustomEvent).detail as string - console.log(' DELETING ANNOTATION BY ID: ', annotationId) for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) { const annotations = await instance.getAnnotations(pageIdx) for (let annIdx = 0; annIdx < annotations.size; annIdx++) { @@ -433,7 +430,6 @@ export default function PdfArticleContainer( continue } const storedId = annotationOmnivoreId(annotation) - console.log(' --- storedId:', storedId) if (storedId == annotationId) { await instance.delete(annotation) await deleteHighlightMutation(annotationId) @@ -495,7 +491,8 @@ export default function PdfArticleContainer( {props.showHighlightsModal && ( { console.log( @@ -511,6 +508,10 @@ export default function PdfArticleContainer( }) props.setShowHighlightsModal(false) }} + viewHighlightInReader={(highlightId) => { + // TODO: scroll to highlight in PDF + props.setShowHighlightsModal(false) + }} /> )} diff --git a/packages/web/components/templates/homeFeed/HighlightItem.tsx b/packages/web/components/templates/homeFeed/HighlightItem.tsx index 59cd263cf..acdc83db9 100644 --- a/packages/web/components/templates/homeFeed/HighlightItem.tsx +++ b/packages/web/components/templates/homeFeed/HighlightItem.tsx @@ -1,19 +1,50 @@ +import { Item } from '@radix-ui/react-dropdown-menu' +import Link from 'next/link' +import { useRouter } from 'next/router' import { DotsThreeVertical } from 'phosphor-react' import { useCallback } from 'react' import { Highlight } from '../../../lib/networking/fragments/highlightFragment' +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 { Box } from '../../elements/LayoutPrimitives' +import { + Dropdown, + DropdownOption, + DropdownSeparator, +} from '../../elements/DropdownElements' +import { Box, SpanBox } from '../../elements/LayoutPrimitives' -import { theme } from '../../tokens/stitches.config' +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(() => { const quote = props.highlight.quote @@ -69,6 +100,28 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element { }} title="Delete" /> + + + { + 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 + + ) } diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 5acb20656..f0519a710 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -1,5 +1,6 @@ +import { useRouter } from 'next/router' import { HighlighterCircle } from 'phosphor-react' -import { useCallback, useEffect, useMemo, useReducer, useState } from 'react' +import { useCallback, useEffect, useReducer, useState } from 'react' import { Toaster } from 'react-hot-toast' import { Highlight } from '../../../lib/networking/fragments/highlightFragment' import { @@ -361,6 +362,8 @@ type HighlightListProps = { } function HighlightList(props: HighlightListProps): JSX.Element { + const router = useRouter() + const exportHighlights = useCallback(() => { ;(async () => { if (!props.item.node.highlights) { @@ -373,6 +376,36 @@ 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 ( - + {props.viewer && ( + + )} ) diff --git a/packages/web/lib/appConfig.ts b/packages/web/lib/appConfig.ts index d28608bec..cf5c573a5 100644 --- a/packages/web/lib/appConfig.ts +++ b/packages/web/lib/appConfig.ts @@ -4,7 +4,6 @@ type AppEnvironment = 'prod' | 'dev' | 'demo' | 'local' type BaseURLs = { webBaseURL: string serverBaseURL: string - highlightsBaseURL: string } type BaseURLRecords = Record @@ -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) diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index c0f459b38..9b175684d 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -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 diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index dec8d6c90..e0388352b 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -346,7 +346,7 @@ export default function Home(): JSX.Element { article={article} showHighlightsModal={showHighlightsModal} setShowHighlightsModal={setShowHighlightsModal} - viewerUsername={viewerData.me?.profile?.username} + viewer={viewerData.me} /> ) : ( {article && viewerData?.me ? ( (undefined) + const [contentProps, setContentProps] = + useState(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(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 (