global: support day format options & $day$ title format & /day command

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2025-08-20 10:24:52 +05:00
committed by Ammar Ahmed
parent 67d47c283e
commit 5c21c0a498
17 changed files with 169 additions and 15 deletions

View File

@@ -57,7 +57,7 @@ import { ScopedThemeProvider } from "../theme-provider";
import { useStore as useThemeStore } from "../../stores/theme-store";
import { writeToClipboard } from "../../utils/clipboard";
import { useEditorStore } from "../../stores/editor-store";
import { parseInternalLink } from "@notesnook/core";
import { DayFormat, parseInternalLink } from "@notesnook/core";
import Skeleton from "react-loading-skeleton";
import useMobile from "../../hooks/use-mobile";
import useTablet from "../../hooks/use-tablet";
@@ -108,6 +108,7 @@ type TipTapProps = {
doubleSpacedLines: boolean;
dateFormat: string;
timeFormat: TimeFormat;
dayFormat: DayFormat;
markdownShortcuts: boolean;
fontLigatures: boolean;
};
@@ -183,6 +184,7 @@ function TipTap(props: TipTapProps) {
doubleSpacedLines,
dateFormat,
timeFormat,
dayFormat,
markdownShortcuts,
fontLigatures
} = props;
@@ -262,6 +264,7 @@ function TipTap(props: TipTapProps) {
doubleSpacedLines,
dateFormat,
timeFormat,
dayFormat,
element: editorContainer(),
editable: !readonly,
content: content?.(),
@@ -431,6 +434,7 @@ function TipTap(props: TipTapProps) {
doubleSpacedLines,
dateFormat,
timeFormat,
dayFormat,
markdownShortcuts,
fontLigatures
]);
@@ -536,6 +540,7 @@ function TiptapWrapper(
| "doubleSpacedLines"
| "dateFormat"
| "timeFormat"
| "dayFormat"
| "markdownShortcuts"
| "fontLigatures"
>
@@ -552,6 +557,7 @@ function TiptapWrapper(
);
const dateFormat = useSettingsStore((store) => store.dateFormat);
const timeFormat = useSettingsStore((store) => store.timeFormat);
const dayFormat = useSettingsStore((store) => store.dayFormat);
const markdownShortcuts = useSettingsStore(
(store) => store.markdownShortcuts
);
@@ -631,13 +637,14 @@ function TiptapWrapper(
}}
>
<TipTap
key={`tiptap-${props.id}-${doubleSpacedLines}-${dateFormat}-${timeFormat}-${markdownShortcuts}-${fontLigatures}`}
key={`tiptap-${props.id}-${doubleSpacedLines}-${dateFormat}-${timeFormat}-${dayFormat}-${markdownShortcuts}-${fontLigatures}`}
{...props}
isMobile={isMobile}
isTablet={isTablet}
doubleSpacedLines={doubleSpacedLines}
dateFormat={dateFormat}
timeFormat={timeFormat}
dayFormat={dayFormat}
markdownShortcuts={markdownShortcuts}
fontLigatures={fontLigatures}
onLoad={(editor) => {

View File

@@ -24,7 +24,7 @@ import {
useStore as useSettingStore
} from "../../stores/setting-store";
import dayjs from "dayjs";
import { TimeFormat } from "@notesnook/core";
import { TimeFormat, DayFormat } from "@notesnook/core";
import { TrashCleanupInterval } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { checkFeature } from "../../common";
@@ -141,6 +141,26 @@ export const BehaviourSettings: SettingsGroup[] = [
]
}
]
},
{
key: "day-format",
title: strings.dayFormat(),
description: strings.dayFormatDesc(),
keywords: [],
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.dayFormat, listener),
components: [
{
type: "dropdown",
onSelectionChanged: (value) =>
useSettingStore.getState().setDayFormat(value as DayFormat),
selectedOption: () => useSettingStore.getState().dayFormat,
options: [
{ value: "short", title: "Short (Mon, Tue)" },
{ value: "long", title: "Long (Monday, Tuesday)" }
]
}
]
}
]
},

View File

@@ -23,7 +23,7 @@ import { desktop } from "../common/desktop-bridge";
import createStore from "../common/store";
import Config from "../utils/config";
import BaseStore from "./index";
import { TimeFormat } from "@notesnook/core";
import { TimeFormat, DayFormat } from "@notesnook/core";
import { Profile, TrashCleanupInterval } from "@notesnook/core";
import { showToast } from "../utils/toast";
import { ConfirmDialog } from "../dialogs/confirm";
@@ -68,6 +68,7 @@ class SettingStore extends BaseStore<SettingStore> {
hideNoteTitle = Config.get("hideNoteTitle", false);
dateFormat = "DD-MM-YYYY";
timeFormat: TimeFormat = "12-hour";
dayFormat: DayFormat = "short";
titleFormat = "Note $date$ $time$";
profile?: Profile;
@@ -95,6 +96,7 @@ class SettingStore extends BaseStore<SettingStore> {
this.set({
dateFormat: db.settings.getDateFormat(),
timeFormat: db.settings.getTimeFormat(),
dayFormat: db.settings.getDayFormat(),
titleFormat: db.settings.getTitleFormat(),
trashCleanupInterval: db.settings.getTrashCleanupInterval(),
profile: db.settings.getProfile(),
@@ -121,6 +123,11 @@ class SettingStore extends BaseStore<SettingStore> {
this.set({ timeFormat });
};
setDayFormat = async (dayFormat: DayFormat) => {
await db.settings.setDayFormat(dayFormat);
this.set({ dayFormat });
};
setTitleFormat = async (titleFormat: string) => {
await db.settings.setTitleFormat(titleFormat);
this.set({ titleFormat });

View File

@@ -27,6 +27,7 @@ Notesnook supports the following (Markdown) shortcuts in the editor:
| Codeblock | \`\`\`javascript<br>function hello() { }<br>\`\`\` |
| Math block | $$$<br>2 + 2 = 4<br>$$$ |
| Current Date | `/date` |
| Current Day | `/day` |
| Date Time | `/time` |
| Current Date & Time | `/now` |
| Current Date & Time with timezone | `/nowz` |

View File

@@ -28,6 +28,8 @@ Go to `Settings` > `Editor` > `Title format` to customize the title formatting.
**$date$**: Today's date
**$day$**: Today's day name
**$time$**: The current time
**$count$**: Current note count + 1

View File

@@ -137,6 +137,7 @@ export class Notes implements ICollection {
this.db.settings.getTitleFormat(),
this.db.settings.getDateFormat(),
this.db.settings.getTimeFormat(),
this.db.settings.getDayFormat(),
headlineTitle,
this.totalNotes
);

View File

@@ -30,7 +30,8 @@ import {
ToolbarConfig,
ToolbarConfigPlatforms,
TrashCleanupInterval,
TimeFormat
TimeFormat,
DayFormat
} from "../types.js";
import { ICollection } from "./collection.js";
import { SQLCachedCollection } from "../database/sql-cached-collection.js";
@@ -53,6 +54,7 @@ const DEFAULT_GROUP_OPTIONS = (key: GroupingKey) =>
const defaultSettings: SettingItemMap = {
timeFormat: "12-hour",
dayFormat: "short",
dateFormat: "DD-MM-YYYY",
titleFormat: "Note $date$ $time$",
defaultNotebook: undefined,
@@ -204,6 +206,14 @@ export class Settings implements ICollection {
return this.set("timeFormat", format);
}
getDayFormat() {
return this.get("dayFormat");
}
setDayFormat(format: DayFormat) {
return this.set("dayFormat", format);
}
getSideBarOrder(section: SideBarSection) {
return this.get(`sideBarOrder:${section}`);
}

View File

@@ -21,6 +21,7 @@ import { Cipher } from "@notesnook/crypto";
import { isCipher } from "./utils/index.js";
export type TimeFormat = "12-hour" | "24-hour";
export type DayFormat = "short" | "long";
export type SortOptions = {
sortBy:
@@ -472,6 +473,7 @@ export type SettingItemMap = {
trashCleanupInterval: TrashCleanupInterval;
titleFormat: string;
timeFormat: TimeFormat;
dayFormat: DayFormat;
dateFormat: string;
defaultNotebook: string | undefined;
defaultTag: string | undefined;

View File

@@ -24,6 +24,7 @@ import { test, expect, describe, beforeAll, afterAll } from "vitest";
const templates = {
$time$: "11:25",
$date$: "DD-MM-YYYY",
$day$: "Wed",
$timestamp$: "DDMMYYYY1125",
$count$: "1",
$headline$: "HEADLINE"
@@ -45,8 +46,35 @@ afterAll(() => {
describe("pairs should be equal", () => {
test.each(cases)("%s", (one, two) => {
expect(formatTitle(one, "[DD-MM-YYYY]", "[hh:mm]", "HEADLINE", 0)).toBe(
two
);
expect(
formatTitle(one, "[DD-MM-YYYY]", "[hh:mm]", "short", "HEADLINE", 0)
).toBe(two);
});
});
describe("day format", () => {
test("$day$ with long format", () => {
expect(
formatTitle(
"Note $day$",
"[DD-MM-YYYY]",
"[hh:mm]",
"long",
"HEADLINE",
0
)
).toBe("Note Wednesday");
});
test("$day$ with short format", () => {
expect(
formatTitle(
"Note $day$",
"[DD-MM-YYYY]",
"[hh:mm]",
"short",
"HEADLINE",
0
)
).toBe("Note Wed");
});
});

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import dayjs from "dayjs";
import advancedFormat from "dayjs/plugin/advancedFormat.js";
import timezone from "dayjs/plugin/timezone.js";
import { TimeFormat } from "../types.js";
import { TimeFormat, DayFormat } from "../types.js";
dayjs.extend(advancedFormat);
dayjs.extend(timezone);
@@ -68,6 +68,10 @@ export function getTimeFormat(format: TimeFormat) {
return format === "12-hour" ? "hh:mm A" : "HH:mm";
}
function getDayFormat(format: DayFormat) {
return format === "short" ? "ddd" : "dddd";
}
export type TimeZoneOptions = {
type: "timezone";
};
@@ -79,6 +83,10 @@ export type DateOptions = {
type: "date";
dateFormat: string;
};
export type DayOptions = {
type: "day";
dayFormat: DayFormat;
};
export type DateTimeOptions = {
type: "date-time";
dateFormat: string;
@@ -93,6 +101,7 @@ export type FormatDateOptions =
| TimeZoneOptions
| TimeOptions
| DateOptions
| DayOptions
| DateTimeOptions
| DateTimeWithTimeZoneOptions;
@@ -117,6 +126,8 @@ export function formatDate(
return dayjs(date).format(getTimeFormat(options.timeFormat));
case "date":
return dayjs(date).format(options.dateFormat);
case "day":
return dayjs(date).format(getDayFormat(options.dayFormat));
case "timezone":
return dayjs(date).format("ZZ");
}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { TimeFormat } from "../types.js";
import { TimeFormat, DayFormat } from "../types.js";
import { formatDate } from "./date.js";
export const NEWLINE_STRIP_REGEX = /[\r\n\t\v]+/gm;
@@ -26,6 +26,7 @@ export const HEADLINE_REGEX = /\$headline\$/g;
const DATE_REGEX = /\$date\$/g;
const COUNT_REGEX = /\$count\$/g;
const TIME_REGEX = /\$time\$/g;
const DAY_REGEX = /\$day\$/g;
const TIMESTAMP_REGEX = /\$timestamp\$/g;
const TIMESTAMP_Z_REGEX = /\$timestampz\$/g;
const DATE_TIME_STRIP_REGEX = /[\\\-:./, ]/g;
@@ -34,6 +35,7 @@ export function formatTitle(
titleFormat: string,
dateFormat: string,
timeFormat: TimeFormat,
dayFormat: DayFormat,
headline = "",
totalNotes = 0
) {
@@ -49,6 +51,11 @@ export function formatTitle(
const timezone = formatDate(Date.now(), {
type: "timezone"
});
const day = formatDate(Date.now(), {
dayFormat,
type: "day"
});
const timestamp = `${date}${time}`.replace(DATE_TIME_STRIP_REGEX, "");
const timestampWithTimeZone = `${timestamp}${timezone}`;
@@ -56,6 +63,7 @@ export function formatTitle(
.replace(NEWLINE_STRIP_REGEX, " ")
.replace(DATE_REGEX, date)
.replace(TIME_REGEX, time)
.replace(DAY_REGEX, day)
.replace(HEADLINE_REGEX, headline || "")
.replace(TIMESTAMP_REGEX, timestamp)
.replace(TIMESTAMP_Z_REGEX, timestampWithTimeZone)

View File

@@ -32,11 +32,17 @@ declare module "@tiptap/core" {
* Insert time at current position
*/
insertTime: () => ReturnType;
/**
* Insert date at current position
*/
insertDate: () => ReturnType;
/**
* Insert day at current position
*/
insertDay: () => ReturnType;
/**
* Insert date & time at current position
*/
@@ -53,6 +59,7 @@ declare module "@tiptap/core" {
export type DateTimeOptions = {
dateFormat: string;
timeFormat: "12-hour" | "24-hour";
dayFormat: "short" | "long";
};
export const DateTime = Extension.create<DateTimeOptions>({
@@ -61,7 +68,8 @@ export const DateTime = Extension.create<DateTimeOptions>({
addOptions() {
return {
dateFormat: "DD-MM-YYYY",
timeFormat: "12-hour"
timeFormat: "12-hour",
dayFormat: "short"
};
},
@@ -98,6 +106,15 @@ export const DateTime = Extension.create<DateTimeOptions>({
type: "time"
})
),
insertDay:
() =>
({ commands }) =>
commands.insertContent(
formatDate(Date.now(), {
dayFormat: this.options.dayFormat,
type: "day"
})
),
insertDateTime:
() =>
({ commands }) =>
@@ -141,6 +158,15 @@ export const DateTime = Extension.create<DateTimeOptions>({
});
}
}),
shortcutInputRule({
shortcut: "/day",
replace: () => {
return formatDate(Date.now(), {
dayFormat: this.options.dayFormat,
type: "day"
});
}
}),
shortcutInputRule({
shortcut: "/now",
replace: () => {
@@ -206,7 +232,8 @@ function textInputRule(config: {
export function replaceDateTime(
value: string,
dateFormat = "DD-MM-YYYY",
timeFormat: "12-hour" | "24-hour" = "12-hour"
timeFormat: "12-hour" | "24-hour" = "12-hour",
dayFormat: DateTimeOptions["dayFormat"] = "short"
) {
value = value.replaceAll(
"/time ",
@@ -242,5 +269,13 @@ export function replaceDateTime(
}) + " "
);
value = value.replaceAll(
"/day ",
formatDate(Date.now(), {
dayFormat: dayFormat,
type: "day"
}) + " "
);
return value;
}

View File

@@ -130,7 +130,8 @@ export function TaskListComponent(
e.target.value = replaceDateTime(
e.target.value,
editor.storage.dateFormat,
editor.storage.timeFormat
editor.storage.timeFormat,
editor.storage.dayFormat
);
updateAttributes(
{ title: e.target.value },

View File

@@ -92,6 +92,7 @@ import "simplebar-react/dist/simplebar.min.css";
interface TiptapStorage {
dateFormat?: DateTimeOptions["dateFormat"];
timeFormat?: DateTimeOptions["timeFormat"];
dayFormat?: DateTimeOptions["dayFormat"];
openLink?: (url: string, openInNewTab?: boolean) => void;
downloadAttachment?: (attachment: Attachment) => void;
openAttachmentPicker?: (type: AttachmentType) => void;
@@ -148,6 +149,7 @@ const useTiptap = (
onBeforeCreate,
dateFormat,
timeFormat,
dayFormat,
copyToClipboard,
createInternalLink,
@@ -322,7 +324,7 @@ const useTiptap = (
KeepInView.configure({
scrollIntoViewOnWindowResize: !isMobile
}),
DateTime.configure({ dateFormat, timeFormat }),
DateTime.configure({ dateFormat, timeFormat, dayFormat }),
KeyMap,
WebClipNode,
CheckList,
@@ -375,6 +377,7 @@ const useTiptap = (
onBeforeCreate: ({ editor }) => {
editor.storage.dateFormat = dateFormat;
editor.storage.timeFormat = timeFormat;
editor.storage.dayFormat = dayFormat;
editor.storage.openLink = openLink;
editor.storage.downloadAttachment = downloadAttachment;

View File

@@ -1486,6 +1486,10 @@ msgstr "Choose from pre-built themes or create your own"
msgid "Choose how dates are displayed in the app"
msgstr "Choose how dates are displayed in the app"
#: src/strings.ts:2610
msgid "Choose how day is displayed in the app"
msgstr "Choose how day is displayed in the app"
#: src/strings.ts:1157
msgid "Choose how the new note titles are formatted"
msgstr "Choose how the new note titles are formatted"
@@ -2036,6 +2040,10 @@ msgstr "Date uploaded"
msgid "Day"
msgstr "Day"
#: src/strings.ts:2609
msgid "Day format"
msgstr "Day format"
#: src/strings.ts:2037
msgid "Deactivate"
msgstr "Deactivate"

View File

@@ -1486,6 +1486,10 @@ msgstr ""
msgid "Choose how dates are displayed in the app"
msgstr ""
#: src/strings.ts:2610
msgid "Choose how day is displayed in the app"
msgstr ""
#: src/strings.ts:1157
msgid "Choose how the new note titles are formatted"
msgstr ""
@@ -2025,6 +2029,10 @@ msgstr ""
msgid "Day"
msgstr ""
#: src/strings.ts:2609
msgid "Day format"
msgstr ""
#: src/strings.ts:2037
msgid "Deactivate"
msgstr ""

View File

@@ -2614,5 +2614,7 @@ Use this if changes from other devices are not appearing on this device. This wi
clickToSave: () => t`Click to save`,
lineHeight: () => t`Line height`,
lineHeightDesc: () => t`Adjust the line height of the editor`,
enterTitle: () => t`Enter title`
enterTitle: () => t`Enter title`,
dayFormat: () => t`Day format`,
dayFormatDesc: () => t`Choose how day is displayed in the app`
};