Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions src/components/NoteManager/NoteManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { cn } from "@fluffylabs/shared-ui";
import { twMerge } from "tailwind-merge";
import { useLatestCallback } from "../../hooks/useLatestCallback";
import { type ILocationContext, LocationContext } from "../LocationProvider/LocationProvider";
import { type INotesContext, NotesContext } from "../NotesProvider/NotesProvider";
import type { IDecoratedNote } from "../NotesProvider/types/DecoratedNote";
import type { IStorageNote } from "../NotesProvider/types/StorageNote";
import { areSelectionsEqual } from "../NotesProvider/utils/areSelectionsEqual";
import { type ISelectionContext, SelectionContext } from "../SelectionProvider/SelectionProvider";
import { InactiveNoteSkeleton } from "./components/InactiveNoteSkeleton";
import { NewNote } from "./components/NewNote";
import { NotesList } from "./components/NotesList";
import { useNoteManagerNotes } from "./useNoteManagerNotes";

const DEFAULT_AUTHOR = "";

Expand All @@ -28,29 +28,33 @@ const MemoizedNotesList = memo(NotesList);

function Notes() {
const { locationParams, setLocationParams } = useContext(LocationContext) as ILocationContext;
const { notesReady, activeNotes, notes, handleAddNote, handleDeleteNote, handleUpdateNote } = useContext(
NotesContext,
) as INotesContext;
const {
notesManagerNotes: notes,
activeNotes,
latestHandleAddNote,
sectionTitlesLoaded,
notesReady,
deleteNote,
updateNote,
} = useNoteManagerNotes();
const { selectedBlocks, pageNumber, handleClearSelection } = useContext(SelectionContext) as ISelectionContext;
const keepShowingNewNote = useRef<{ selectionEnd: ISynctexBlockId; selectionStart: ISynctexBlockId }>(undefined);

const latestHandleAddNote = useLatestCallback(handleAddNote);
const latestDeleteNote = useLatestCallback(handleDeleteNote);
const latestUpdateNote = useLatestCallback(handleUpdateNote);
const latestHandleClearSelection = useLatestCallback(handleClearSelection);

const memoizedHandleDeleteNote = useCallback(
(note: IDecoratedNote) => {
latestDeleteNote.current(note);
handleClearSelection();
deleteNote(note);
latestHandleClearSelection.current();
},
[handleClearSelection, latestDeleteNote],
[latestHandleClearSelection, deleteNote],
);

const memoizedHandleUpdateNote = useCallback(
(note: IDecoratedNote, newNote: IStorageNote) => {
latestUpdateNote.current(note, newNote);
note.original.content = newNote.content;
updateNote(note, newNote);
},
[latestUpdateNote],
[updateNote],
);

const handleNewNoteCancel = useCallback(() => {
Expand Down Expand Up @@ -117,22 +121,24 @@ function Notes() {
[],
);

const isActiveNotes = notes.some((note) => activeNotes.has(note));
const isActiveNotes = notes.some((note) => activeNotes.has(note.noteObject));

const readyAndLoaded = notesReady && sectionTitlesLoaded;

useEffect(() => {
if (notesReady) {
if (readyAndLoaded) {
keepShowingNewNote.current = undefined;
}
}, [notesReady]);
}, [readyAndLoaded]);

return (
<div className={cn("note-manager flex flex-col gap-2.5", !notesReady && "opacity-30 pointer-events-none")}>
<div className={cn("note-manager flex flex-col gap-2.5", !readyAndLoaded && "opacity-30 pointer-events-none")}>
{locationParams.selectionEnd &&
locationParams.selectionStart &&
pageNumber !== null &&
selectedBlocks.length > 0 &&
!isActiveNotes &&
(notesReady || areSelectionsEqual(locationParams, keepShowingNewNote.current)) && (
(readyAndLoaded || areSelectionsEqual(locationParams, keepShowingNewNote.current)) && (
<NewNote
selectionStart={locationParams.selectionStart}
selectionEnd={locationParams.selectionEnd}
Expand All @@ -142,7 +148,7 @@ function Notes() {
/>
)}

{!notesReady && notes.length === 0 && (
{!readyAndLoaded && notes.length === 0 && (
<>
<InactiveNoteSkeleton />
<InactiveNoteSkeleton />
Expand All @@ -151,7 +157,9 @@ function Notes() {
</>
)}

{notesReady && notes.length === 0 && <div className="no-notes text-sidebar-foreground">No notes available</div>}
{readyAndLoaded && notes.length === 0 && (
<div className="no-notes text-sidebar-foreground">No notes available</div>
)}

{notes.length > 0 && (
<MemoizedNotesList
Expand Down
64 changes: 44 additions & 20 deletions src/components/NoteManager/components/NewNote.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { ISynctexBlockId } from "@fluffylabs/links-metadata";
import { Button } from "@fluffylabs/shared-ui";
import { type ChangeEvent, type ChangeEventHandler, useCallback, useMemo, useRef, useState } from "react";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { useLatestCallback } from "../../../hooks/useLatestCallback";
import { validateMath } from "../../../utils/validateMath";
import { CodeSyncContext, type ICodeSyncContext } from "../../CodeSyncProvider/CodeSyncProvider";
import { type IDecoratedNote, NoteSource } from "../../NotesProvider/types/DecoratedNote";
import type { INoteV3 } from "../../NotesProvider/types/StorageNote";
import type { ISingleNoteContext } from "./NoteContext";
Expand All @@ -18,7 +19,6 @@ type NewNoteProps = {
};

export const NewNote = ({ version, onCancel, onSave, selectionStart, selectionEnd }: NewNoteProps) => {
const [noteContent, setNoteContent] = useState("");
const [noteContentError, setNoteContentError] = useState<string | null>(null);
const [labels, setLabels] = useState<string[]>(["local"]);

Expand All @@ -30,6 +30,8 @@ export const NewNote = ({ version, onCancel, onSave, selectionStart, selectionEn
}, [latestOnCancel]);

const handleSaveClick = useCallback(() => {
const noteContent = textAreaRef.current?.value ?? "";

const mathValidationError = validateMath(noteContent);
if (mathValidationError) {
setNoteContentError(mathValidationError);
Expand All @@ -41,25 +43,19 @@ export const NewNote = ({ version, onCancel, onSave, selectionStart, selectionEn
}

latestOnSave.current({ noteContent, labels });
}, [noteContent, latestOnSave, labels]);
}, [latestOnSave, labels]);

const currentVersionLink = "";
const originalVersionLink = "";

const handleNoteContentChange = useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
setNoteContent(event.target.value);
setNoteContentError(null);
}, []);

const noteDirty = useDumbDirtyNoteObj({ noteContent, labels, version });
const noteDirty = useDumbDirtyNoteObj({ labels, version });
const note = useDumbNoteObj({ version, selectionStart, selectionEnd });

const noteLayoutContext = useNewNoteLayoutContext({
note,
noteDirty,
currentVersionLink,
handleCancelClick,
handleNoteContentChange,
handleNoteLabelsChange: setLabels,
handleSaveClick,
originalVersionLink,
Expand Down Expand Up @@ -140,51 +136,76 @@ const useDumbNoteObj = ({
);

const useDumbDirtyNoteObj = ({
noteContent,
labels,
version,
}: {
noteContent: string;
labels: string[];
version: string;
}) =>
useMemo(
() =>
({
author: "",
content: noteContent,
content: "",
labels,
date: 0,
noteVersion: 3,
selectionEnd: createDumbISyntexBlockId(),
selectionStart: createDumbISyntexBlockId(),
version,
}) satisfies INoteV3,
[noteContent, version, labels],
[version, labels],
);

const useNewNoteLayoutContext = ({
handleSaveClick,
handleCancelClick,
note,
noteDirty,
handleNoteContentChange,
handleNoteLabelsChange,
currentVersionLink,
originalVersionLink,
selectionEnd,
selectionStart,
}: {
handleSaveClick: () => void;
handleCancelClick: () => void;
note: IDecoratedNote;
noteDirty: INoteV3;
handleNoteContentChange: ChangeEventHandler<HTMLTextAreaElement>;
handleNoteLabelsChange: (labels: string[]) => void;
currentVersionLink: string;
originalVersionLink: string;
selectionStart: ISynctexBlockId;
selectionEnd: ISynctexBlockId;
}) =>
useMemo(
}) => {
const { getSectionTitleAtSynctexBlock, getSubsectionTitleAtSynctexBlock } = useContext(
CodeSyncContext,
) as ICodeSyncContext;

const [sectionTitles, setSectionTitles] = useState<{ sectionTitle: string; subSectionTitle: string }>({
sectionTitle: "",
subSectionTitle: "",
});

useEffect(() => {
let cancelled = false;
(async () => {
if (selectionStart && selectionEnd) {
const newSectionTitle = (await getSectionTitleAtSynctexBlock(selectionStart)) ?? "";
const newSubSectionTitle = (await getSubsectionTitleAtSynctexBlock(selectionStart)) ?? "";
if (cancelled) return;
setSectionTitles({
sectionTitle: newSectionTitle,
subSectionTitle: newSubSectionTitle,
});
}
})();
return () => {
cancelled = true;
};
}, [selectionStart, selectionEnd, getSectionTitleAtSynctexBlock, getSubsectionTitleAtSynctexBlock]);

const context = useMemo(
() =>
({
active: true,
Expand All @@ -196,21 +217,24 @@ const useNewNoteLayoutContext = ({
isEditing: true,
note,
noteDirty,
handleNoteContentChange,
handleNoteLabelsChange,
handleSelectNote: () => {},
noteOriginalVersionShort: "",
currentVersionLink,
originalVersionLink,
sectionTitles,
}) satisfies ISingleNoteContext,
[
noteDirty,
handleNoteContentChange,
handleNoteLabelsChange,
note,
handleCancelClick,
handleSaveClick,
currentVersionLink,
originalVersionLink,
sectionTitles,
],
);

return context;
};
Loading
Loading