Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to start week on Monday #17

Merged
merged 3 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions src/constants/Settings.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export const SHOW_CALENDAR_BUTTON = "showCalendarToggleOnToolbar";
export const SHOW_MODIFIED_NOTES = "showModifiedNotes";
export const WEEK_START_DAY = "weekStartDay";

export enum WeekStartDay {
Sunday = "Sunday",
Monday = "Monday",
}
13 changes: 13 additions & 0 deletions src/gui/Calendar/CalendarHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { weekdaysShort } from "moment";
import React from "react";
import styled from "styled-components";
import useOnSettingsChange from "../hooks/useOnSettingsChange";
import { WEEK_START_DAY, WeekStartDay } from "@constants/Settings";

const HeaderCell = styled.th`
font-size: var(--joplin-font-size);
Expand All @@ -19,6 +21,17 @@ function CalendarHeader(props: CalendarHeaderProps) {
const calendarHeader = weekdaysShort().map((day) => (
<HeaderCell>{day}</HeaderCell>
));

const weekStartDay = useOnSettingsChange<WeekStartDay>(
WEEK_START_DAY,
WeekStartDay.Sunday
);

// Need to shift the headers based on the week start day
if (weekStartDay === WeekStartDay.Monday) {
calendarHeader.push(calendarHeader.shift());
}

return <HeaderRow>{...calendarHeader}</HeaderRow>;
}

Expand Down
28 changes: 28 additions & 0 deletions src/gui/Calendar/__tests__/Calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import moment from "moment";
import { act } from "react-dom/test-utils";
import useGetMonthStatistics from "../../hooks/useGetMonthStatistics";
import useWebviewApiOnMessage from "../../hooks/useWebViewApiOnMessage";
import { WeekStartDay } from "@constants/Settings";
import useOnSettingsChange from "../../hooks/useOnSettingsChange";

jest.mock("../../hooks/useGetMonthStatistics");
const mockedUseGetMonthStatistics = jest.mocked(useGetMonthStatistics);

jest.mock("../../hooks/useOnSettingsChange");
const mockedUseOnSettingsChange = jest.mocked(useOnSettingsChange);

global.webviewApi = {
postMessage: jest.fn(),
onMessage: jest.fn(),
Expand All @@ -26,6 +31,7 @@ describe("calendar", () => {
},
refetch: jest.fn(),
});
mockedUseOnSettingsChange.mockReset();
});

it("displays dates correctly", () => {
Expand All @@ -51,6 +57,28 @@ describe("calendar", () => {
}
});

it("displays dates correctly if week starts on Monday", () => {
mockedUseOnSettingsChange.mockReturnValue(WeekStartDay.Monday);

const date = moment("May-29-2023", "MMM-DD-YYYY");
render(<Calendar selectedDate={date} />);

expect(screen.getByText("May 2023")).toBeDefined();

const cells = screen.getAllByRole("cell");
expect(cells).toHaveLength(42); // 7 days * 6 rows

// Assert May (No April)
for (let i = 0; i <= 30; i++) {
expect(cells[i].textContent).toEqual((i + 1).toString());
}

// Assert June
for (let i = 1; i <= 11; i++) {
expect(cells[i + 30].textContent).toEqual(i.toString());
}
});

it("calls callback when next month clicked", () => {
const nextMonthCallback = jest.fn();
const date = moment("May-29-2023", "MMM-DD-YYYY");
Expand Down
27 changes: 25 additions & 2 deletions src/gui/Calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import useGetMonthStatistics from "../hooks/useGetMonthStatistics";
import MsgType from "@constants/messageTypes";
import { PluginPostMessage } from "@constants/pluginMessageTypes";
import useWebviewApiOnMessage from "../hooks/useWebViewApiOnMessage";
import useOnSettingsChange from "../hooks/useOnSettingsChange";
import { WEEK_START_DAY, WeekStartDay } from "@constants/Settings";

const DAYS_IN_A_WEEK = 7;
const CALENDAR_ROWS = 6;
Expand Down Expand Up @@ -78,16 +80,37 @@ function Calendar({
[onKeyboardNavigation]
);

const weekStartDay = useOnSettingsChange<WeekStartDay>(
WEEK_START_DAY,
WeekStartDay.Sunday
);

const currentMonthFirstDay = shownMonth.startOf("month");

const calendarBody: React.JSX.Element[] = [];
const firstRowOffset = -currentMonthFirstDay.weekday();
let firstRowBacktrackOffset;
if (weekStartDay === WeekStartDay.Monday) {
// If the current month starts on a day other than Monday, we need to backtrack.
// Monday has isoWeek 1, and we need to backtrack 0 days.
// Tuesday has isoWeek 2, and we need to backtrack 1 days.
// ...
// Sunday has isoWeek 7, and we need to backtrack 6 days.
firstRowBacktrackOffset = currentMonthFirstDay.isoWeekday() - 1;
} else {
// Fallback to asssuming week starts on Sunday.
// If current month starts on a day other than Sunday, we need to backtrack.
// Monday has isoWeek 1, and we need to backtrack 1 day.
// Tuesday has isoWeek 2, and we need to backtrack 2 days.
// ...
// Sunday has isoWeek 7, and we need to backtrack no days.
firstRowBacktrackOffset = currentMonthFirstDay.isoWeekday() % 7;
}

// Note: Moment JS uses in place operations
const workingDate = currentMonthFirstDay.clone();

// Offset to fill in dates from previous month till first of current month.
workingDate.add(firstRowOffset, "days");
workingDate.subtract(firstRowBacktrackOffset, "days");

for (let row = 0; row < CALENDAR_ROWS; row++) {
const cols: React.JSX.Element[] = [];
Expand Down
33 changes: 5 additions & 28 deletions src/gui/hooks/useNoteSearchTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,22 @@ import MsgType from "@constants/messageTypes";
import { useEffect, useState } from "react";
import useWebviewApiOnMessage from "./useWebViewApiOnMessage";
import { SHOW_MODIFIED_NOTES } from "@constants/Settings";
import useOnSettingsChange from "./useOnSettingsChange";

/**
* Provides note types to search for when fetching notes, based on user preference.
*
* Note type examples: Created Notes, Modified Notes
*/
function useNoteSearchTypes() {
const [showModifiedNotes, setShowModifiedNotes] = useState(false);
const showModifiedNotes = useOnSettingsChange<boolean>(
SHOW_MODIFIED_NOTES,
false
);
const noteSearchTypes = [NoteSearchTypes.Created];
if (showModifiedNotes) {
noteSearchTypes.push(NoteSearchTypes.Modified);
}

// Trigger all settings callbacks once initialized
// to prevent any race conditions.
useEffect(() => {
webviewApi.postMessage({
type: MsgType.TriggerAllSettingsCallbacks,
});
}, []);

useWebviewApiOnMessage((data) => {
const message = data.message;

if (!message.type) {
return;
}
if (message.type !== MsgType.SettingChanged) {
return;
}

const settingMessage = message as any;

if (settingMessage.key !== SHOW_MODIFIED_NOTES) {
return;
}
setShowModifiedNotes((message as any).value);
});

return noteSearchTypes;
}

Expand Down
48 changes: 48 additions & 0 deletions src/gui/hooks/useOnSettingsChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import MsgType from "@constants/messageTypes";
import { useEffect, useState } from "react";
import useWebviewApiOnMessage from "./useWebViewApiOnMessage";

/**
* Providers the settings value for the given settings key.
*
* @template SettingType The type of the settings value
*
* @param settingKey The settings key
* @returns The settings value
*/
function useOnSettingsChange<SettingType>(
settingKey: string,
defaultValue: SettingType = null
): SettingType {
const [settingValue, setSettingValue] = useState<SettingType>(defaultValue);

// Trigger all settings callbacks once initialized
// to prevent any race conditions.
useEffect(() => {
webviewApi.postMessage({
type: MsgType.TriggerAllSettingsCallbacks,
});
}, []);

useWebviewApiOnMessage((data) => {
const message = data.message;

if (!message.type) {
return;
}
if (message.type !== MsgType.SettingChanged) {
return;
}

const settingMessage = message as any;

if (settingMessage.key !== settingKey) {
return;
}
setSettingValue((message as any).value);
});

return settingValue;
}

export default useOnSettingsChange;
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
registerSettings,
triggerAllSettingsCallbacks,
onSettingChange,
onSettingChangeAlertPanel,
registerPanelAlertOnSettingChange,
} from "./settings";
import { SHOW_CALENDAR_BUTTON, SHOW_MODIFIED_NOTES } from "@constants/Settings";
import { SHOW_CALENDAR_BUTTON } from "@constants/Settings";

joplin.plugins.register({
onStart: async function () {
Expand All @@ -33,7 +33,7 @@ joplin.plugins.register({
}
});

await onSettingChangeAlertPanel(panel, SHOW_MODIFIED_NOTES);
await registerPanelAlertOnSettingChange(panel);

await triggerAllSettingsCallbacks();
},
Expand Down
25 changes: 24 additions & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import joplin from "api";
import { SettingItemType } from "api/types";
import MsgType from "@constants/messageTypes";
import { SHOW_CALENDAR_BUTTON, SHOW_MODIFIED_NOTES } from "@constants/Settings";
import {
SHOW_CALENDAR_BUTTON,
SHOW_MODIFIED_NOTES,
WEEK_START_DAY,
WeekStartDay,
} from "@constants/Settings";

const SETTINGS_SECTION_ID = "joplinCalendarSection";

Expand Down Expand Up @@ -32,6 +37,16 @@ export async function registerSettings() {
value: true,
section: SETTINGS_SECTION_ID,
},
[WEEK_START_DAY]: {
label: "Week Start Day",
description: "Which day the week starts on",
public: true,
isEnum: true,
type: SettingItemType.String,
options: WeekStartDay,
value: WeekStartDay.Sunday,
section: SETTINGS_SECTION_ID,
},
});

await joplin.settings.onChange(async ({ keys }) => {
Expand All @@ -43,6 +58,14 @@ export async function registerSettings() {
});
}

/**
* Alerts panel when certain settings change.
*/
export async function registerPanelAlertOnSettingChange(panelHandle: string) {
await onSettingChangeAlertPanel(panelHandle, SHOW_MODIFIED_NOTES);
await onSettingChangeAlertPanel(panelHandle, WEEK_START_DAY);
}

export async function triggerAllSettingsCallbacks() {
Object.entries(settingObservers).forEach(async ([key, callbacks]) => {
callbacks.forEach(async (callback) => {
Expand Down
Loading