Skip to content

Commit

Permalink
Implement Related Notes
Browse files Browse the repository at this point in the history
  • Loading branch information
rsandz committed Feb 19, 2024
1 parent 913b544 commit d773ec5
Show file tree
Hide file tree
Showing 13 changed files with 371 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/constants/NoteSearchTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
enum NoteSearchTypes {
Created,
Modified,
Related,
}

export default NoteSearchTypes;
1 change: 1 addition & 0 deletions src/constants/Settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const SHOW_CALENDAR_BUTTON = "showCalendarToggleOnToolbar";
export const SHOW_MODIFIED_NOTES = "showModifiedNotes";
export const SHOW_RELATED_NOTES = "showRelatedNotes";
export const WEEK_START_DAY = "weekStartDay";

export enum WeekStartDay {
Expand Down
35 changes: 33 additions & 2 deletions src/gui/NoteList/NoteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function NoteList(props: NoteListProps) {
if (message.type === MsgType.NoteChanged) {
refetchCreatedNotes();
refetchModifiedNotes();
refetchRelatedNotes();
refetchSelectedNote();
}
});
Expand Down Expand Up @@ -118,6 +119,21 @@ function NoteList(props: NoteListProps) {
enabled: noteSearchTypes.includes(NoteSearchTypes.Modified),
});

const { data: relatedNotesData, refetch: refetchRelatedNotes } = useQuery<
Note[]
>({
queryKey: ["notes", "related", currentDate.toISOString()],
queryFn: async () => {
console.debug(`Requesting notes for ${currentDate.toLocaleString()}`);
return await webviewApi.postMessage({
type: MsgType.GetNotes,
currentDate: currentDate.toISOString(),
noteSearchTypes: [NoteSearchTypes.Related],
});
},
enabled: noteSearchTypes.includes(NoteSearchTypes.Related),
});

const { data: selectedNote, refetch: refetchSelectedNote } = useQuery<Note>({
queryKey: ["selectedNote"],
queryFn: async () => {
Expand Down Expand Up @@ -158,13 +174,28 @@ function NoteList(props: NoteListProps) {
/>
</ButtonBarContainer>
<ListContainer>
{noteSearchTypes.includes(NoteSearchTypes.Related) && (
<>
<NoteTypeHeader>Related Notes</NoteTypeHeader>
<NoteListItems
notes={relatedNotesData ?? []}
selectedNoteId={selectedNote?.id}
sortBy={sortBy}
sortDirection={sortDirection}
key={`RelatedNotes:{currentDate.toISOString()}`}
primaryTextStrategy={(note) =>
`${moment(note.createdTime).format("LT")}`
}
/>{" "}
</>
)}
<NoteTypeHeader>Created Notes</NoteTypeHeader>
<NoteListItems
notes={createdNotesData ?? []}
selectedNoteId={selectedNote?.id}
sortBy={sortBy}
sortDirection={sortDirection}
key={`CreatedNotes:currentDate.toISOString()`}
key={`CreatedNotes:{currentDate.toISOString()}`}
primaryTextStrategy={(note) =>
`${moment(note.createdTime).format("LT")}`
}
Expand All @@ -177,7 +208,7 @@ function NoteList(props: NoteListProps) {
selectedNoteId={selectedNote?.id}
sortBy={sortBy}
sortDirection={sortDirection}
key={`ModifiedNotes:currentDate.toISOString()`}
key={`ModifiedNotes:{currentDate.toISOString()}`}
primaryTextStrategy={(note) =>
`${moment(note.updatedTime).format("LT")}`
}
Expand Down
8 changes: 5 additions & 3 deletions src/gui/NoteList/__tests__/NoteList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe("NoteList", () => {
postMessageMock.mockReturnValue([]);
render(wrapper(<NoteList currentDate={moment()} />));
await waitFor(() =>
expect(screen.getByText("No Notes Found")).toBeDefined()
expect(screen.getAllByText("No Notes Found")).toHaveLength(1)
);
});

Expand Down Expand Up @@ -109,9 +109,11 @@ describe("NoteList", () => {
},
});
});
await waitFor(() => expect(screen.getByText("Test Title")).toBeDefined());
await waitFor(() =>
expect(screen.getAllByText("Test Title")).toHaveLength(1)
);

expect(screen.getByText("Test Title 2").parentElement).toHaveStyle(
expect(screen.getAllByText("Test Title 2")[0].parentElement).toHaveStyle(
"background-color: var(--joplin-background-color-hover3);"
);
});
Expand Down
12 changes: 11 additions & 1 deletion src/gui/hooks/useNoteSearchTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import NoteSearchTypes from "@constants/NoteSearchTypes";
import MsgType from "@constants/messageTypes";
import { useEffect, useState } from "react";
import useWebviewApiOnMessage from "./useWebViewApiOnMessage";
import { SHOW_MODIFIED_NOTES } from "@constants/Settings";
import { SHOW_MODIFIED_NOTES, SHOW_RELATED_NOTES } from "@constants/Settings";
import useOnSettingsChange from "./useOnSettingsChange";

/**
Expand All @@ -15,10 +15,20 @@ function useNoteSearchTypes() {
SHOW_MODIFIED_NOTES,
false
);
const showRelatedNotes = useOnSettingsChange<boolean>(
SHOW_RELATED_NOTES,
false
);

// Default note types to show
const noteSearchTypes = [NoteSearchTypes.Created];

if (showModifiedNotes) {
noteSearchTypes.push(NoteSearchTypes.Modified);
}
if (showRelatedNotes) {
noteSearchTypes.push(NoteSearchTypes.Related);
}
return noteSearchTypes;
}

Expand Down
7 changes: 7 additions & 0 deletions src/handlers/GetMonthStatistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import MonthStatistics from "@constants/MonthStatistics";
import {
getCreatedNotesForDay,
getModifiedNotesForDay,
getRelatedNotesForDay,
} from "./GetNotesForDay";
import Note from "@constants/Note";

Expand Down Expand Up @@ -61,3 +62,9 @@ export async function getMonthModifiedNoteStatistics(
): Promise<MonthStatistics> {
return getMonthStatistics(date, getModifiedNotesForDay);
}

export async function getMonthRelatedNoteStatistics(
date: moment.Moment
): Promise<MonthStatistics> {
return getMonthStatistics(date, getRelatedNotesForDay);
}
57 changes: 57 additions & 0 deletions src/handlers/GetNearestDayWithNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
convertSnakeCaseKeysToCamelCase,
convertEpochDateInNoteToIsoString,
} from "./Transforms";
import { getDateFormat } from "./GlobalSettings";

/**
* Get notes in past or future matching the specific operator term.
Expand Down Expand Up @@ -77,3 +78,59 @@ export async function getNearestDayWithModifiedNote(
"user_updated_time"
);
}

const RELATED_NOTE_MAX_DAYS_TO_SEARCH = 120; // ~4 months

/**
* Gets nearest day with related notes.
* Related notes are notes that have the day in the title.
*
* @param startDate The date where search will begin from
* @param direction "future" or "past"
*
* @param earlyStopDate If previous notes from other criteria are found, provide
* the date to stop searching at. Increases responsiveness of search.
*/
export async function getNearestDayWithRelatedNote(
startDate: moment.Moment,
direction: "future" | "past",
earlyStopDate: moment.Moment | null
): Promise<GetNearestDayWithNoteResponse | null> {
const dateFormat = await getDateFormat();

const workingDate = startDate.clone();
for (let i = 0; i < RELATED_NOTE_MAX_DAYS_TO_SEARCH; i++) {
// Don't include the startDate
if (direction === "past") {
workingDate.subtract(1, "day");
} else {
workingDate.add(1, "day");
}

if (earlyStopDate && workingDate.isSame(earlyStopDate, "day")) {
return null;
}

const dateString = workingDate.format(dateFormat);
console.error(`Searching for ${dateString}`);

const response = await joplin.data.get(["search"], {
fields: ["id", "title", "user_created_time", "user_updated_time"],
limit: 1,
query: `title:/"${dateString}"`,
});

if (response.items.length > 0) {
let note = response.items[0];
note = removeUserTermFromUserTimes(note);
note = convertSnakeCaseKeysToCamelCase(note);
const transformedNote = convertEpochDateInNoteToIsoString(note);
return {
note: transformedNote,
date: workingDate.toISOString(),
};
}
}

return null;
}
28 changes: 28 additions & 0 deletions src/handlers/GetNotesForDay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
convertSnakeCaseKeysToCamelCase,
convertEpochDateInNoteToIsoString,
} from "./Transforms";
import { getDateFormat } from "./GlobalSettings";

/**
* Get notes for a specific day using the provided operator term.
Expand Down Expand Up @@ -43,3 +44,30 @@ export async function getCreatedNotesForDay(date: moment.Moment) {
export async function getModifiedNotesForDay(date: moment.Moment) {
return getNotesForDay(date, "updated");
}

/**
* Gets list of related notes for a specific day.
* Related notes are notes that have the day in the title.
*/
export async function getRelatedNotesForDay(date: moment.Moment) {
const notes = [];

const dateString = date.format(await getDateFormat());

let page = 1;
let paginatedResponse: Record<string, any>;
do {
paginatedResponse = await joplin.data.get(["search"], {
fields: ["id", "title", "user_created_time", "user_updated_time"],
query: `title:/"${dateString}"`,
page: page,
});
notes.push(...paginatedResponse.items);
page++;
} while (paginatedResponse.has_more);

return notes
.map(removeUserTermFromUserTimes)
.map(convertSnakeCaseKeysToCamelCase)
.map(convertEpochDateInNoteToIsoString);
}
12 changes: 12 additions & 0 deletions src/handlers/GlobalSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import joplin from "api";

const DATE_FORMAT_KEY = "dateFormat";

/**
* Get's the user's date format.
*
* @see https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L799
*/
export async function getDateFormat() {
return (await joplin.settings.globalValue(DATE_FORMAT_KEY)) as string;
}
60 changes: 47 additions & 13 deletions src/handlers/PanelMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import moment from "moment";
import {
getNearestDayWithCreatedNote,
getNearestDayWithModifiedNote,
getNearestDayWithRelatedNote,
} from "./GetNearestDayWithNote";
import {
getMonthCreatedNoteStatistics,
getMonthModifiedNoteStatistics,
getMonthRelatedNoteStatistics,
} from "./GetMonthStatistics";
import {
getCreatedNotesForDay,
getModifiedNotesForDay,
getRelatedNotesForDay,
} from "./GetNotesForDay";
import NoteSearchTypes from "@constants/NoteSearchTypes";
import MonthStatistics from "@constants/MonthStatistics";
Expand Down Expand Up @@ -48,6 +51,13 @@ async function handleGetNotes(message) {
))
);
}
if (noteTypes.includes(NoteSearchTypes.Related)) {
notes.push(
...(await getRelatedNotesForDay(
moment(message.currentDate, moment.ISO_8601)
))
);
}

const uniqueNotes = uniqBy(notes, (note) => note.id);
return uniqueNotes;
Expand Down Expand Up @@ -79,6 +89,12 @@ async function handleGetMonthStatistics(message): Promise<MonthStatistics> {
);
}

if (noteTypes.includes(NoteSearchTypes.Related)) {
individualStatistics.push(
await getMonthRelatedNoteStatistics(moment(message.date, moment.ISO_8601))
);
}

return individualStatistics.reduce(
(prev, curr) => {
const notesPerDay = prev.notesPerDay;
Expand Down Expand Up @@ -107,6 +123,25 @@ async function handleGetNearestDayWithNote(message) {
return null;
}

const reducer = (responses) =>
responses.reduce((prev, curr) => {
if (!curr) {
return prev;
}
if (!prev) {
return curr;
}

const currDate = moment(curr.date, moment.ISO_8601);
const prevDate = moment(prev.date, moment.ISO_8601);

if (message.direction === "future") {
return currDate.isBefore(prevDate) ? curr : prev;
} else {
return currDate.isAfter(prevDate) ? curr : prev;
}
}, null);

const candidateResponses: GetNearestDayWithNoteResponse[] = [];

if (noteTypes.includes(NoteSearchTypes.Created)) {
Expand All @@ -126,19 +161,18 @@ async function handleGetNearestDayWithNote(message) {
);
}

return candidateResponses.reduce((prev, curr) => {
if (!curr) {
return prev;
}
if (!prev) {
return curr;
}
if (message.direction === "future") {
return curr.date < prev.date ? curr : prev;
} else {
return curr.date > prev.date ? curr : prev;
}
}, null);
if (noteTypes.includes(NoteSearchTypes.Related)) {
const earlyStopDate = reducer(candidateResponses);
candidateResponses.push(
await getNearestDayWithRelatedNote(
moment(message.date, moment.ISO_8601),
message.direction,
earlyStopDate
)
);
}

return reducer(candidateResponses);
}

/**
Expand Down
Loading

0 comments on commit d773ec5

Please sign in to comment.