From 5c21c0a498b67b05f8b6a2003fb8a9c846671220 Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Wed, 20 Aug 2025 10:24:52 +0500 Subject: [PATCH] global: support day format options & $day$ title format & /day command Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> --- apps/web/src/components/editor/tiptap.tsx | 11 +++++- .../dialogs/settings/behaviour-settings.ts | 22 ++++++++++- apps/web/src/stores/setting-store.ts | 9 ++++- .../markdown-notes-editing.md | 1 + .../personalizing-rich-text-editor.md | 2 + packages/core/src/collections/notes.ts | 1 + packages/core/src/collections/settings.ts | 12 +++++- packages/core/src/types.ts | 2 + .../src/utils/__tests__/title-format.test.js | 34 ++++++++++++++-- packages/core/src/utils/date.ts | 13 ++++++- packages/core/src/utils/title-format.ts | 10 ++++- .../src/extensions/date-time/date-time.ts | 39 ++++++++++++++++++- .../src/extensions/task-list/component.tsx | 3 +- packages/editor/src/index.ts | 5 ++- packages/intl/locale/en.po | 8 ++++ packages/intl/locale/pseudo-LOCALE.po | 8 ++++ packages/intl/src/strings.ts | 4 +- 17 files changed, 169 insertions(+), 15 deletions(-) diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx index 28da91bce..af36f81c9 100644 --- a/apps/web/src/components/editor/tiptap.tsx +++ b/apps/web/src/components/editor/tiptap.tsx @@ -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( }} > { diff --git a/apps/web/src/dialogs/settings/behaviour-settings.ts b/apps/web/src/dialogs/settings/behaviour-settings.ts index 5dc8e8ef0..f2e31b47b 100644 --- a/apps/web/src/dialogs/settings/behaviour-settings.ts +++ b/apps/web/src/dialogs/settings/behaviour-settings.ts @@ -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)" } + ] + } + ] } ] }, diff --git a/apps/web/src/stores/setting-store.ts b/apps/web/src/stores/setting-store.ts index c11e39463..43c7d0d3f 100644 --- a/apps/web/src/stores/setting-store.ts +++ b/apps/web/src/stores/setting-store.ts @@ -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 { 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 { 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 { 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 }); diff --git a/docs/help/contents/rich-text-editor/markdown-notes-editing.md b/docs/help/contents/rich-text-editor/markdown-notes-editing.md index c5c2fdbaf..7a16f7d17 100644 --- a/docs/help/contents/rich-text-editor/markdown-notes-editing.md +++ b/docs/help/contents/rich-text-editor/markdown-notes-editing.md @@ -27,6 +27,7 @@ Notesnook supports the following (Markdown) shortcuts in the editor: | Codeblock | \`\`\`javascript
function hello() { }
\`\`\` | | Math block | $$$
2 + 2 = 4
$$$ | | Current Date | `/date` | +| Current Day | `/day` | | Date Time | `/time` | | Current Date & Time | `/now` | | Current Date & Time with timezone | `/nowz` | diff --git a/docs/help/contents/rich-text-editor/personalizing-rich-text-editor.md b/docs/help/contents/rich-text-editor/personalizing-rich-text-editor.md index e7c23aca7..dabeb18f1 100644 --- a/docs/help/contents/rich-text-editor/personalizing-rich-text-editor.md +++ b/docs/help/contents/rich-text-editor/personalizing-rich-text-editor.md @@ -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 diff --git a/packages/core/src/collections/notes.ts b/packages/core/src/collections/notes.ts index d0dc70548..0ff1090e9 100644 --- a/packages/core/src/collections/notes.ts +++ b/packages/core/src/collections/notes.ts @@ -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 ); diff --git a/packages/core/src/collections/settings.ts b/packages/core/src/collections/settings.ts index e975c3d7f..0a794114c 100644 --- a/packages/core/src/collections/settings.ts +++ b/packages/core/src/collections/settings.ts @@ -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}`); } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index e32823c8c..a581d132d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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; diff --git a/packages/core/src/utils/__tests__/title-format.test.js b/packages/core/src/utils/__tests__/title-format.test.js index 75de04e4e..d34df0c63 100644 --- a/packages/core/src/utils/__tests__/title-format.test.js +++ b/packages/core/src/utils/__tests__/title-format.test.js @@ -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"); }); }); diff --git a/packages/core/src/utils/date.ts b/packages/core/src/utils/date.ts index 80a448518..d81a16413 100644 --- a/packages/core/src/utils/date.ts +++ b/packages/core/src/utils/date.ts @@ -20,7 +20,7 @@ along with this program. If not, see . 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"); } diff --git a/packages/core/src/utils/title-format.ts b/packages/core/src/utils/title-format.ts index c4498f92b..ce3e41a69 100644 --- a/packages/core/src/utils/title-format.ts +++ b/packages/core/src/utils/title-format.ts @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -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) diff --git a/packages/editor/src/extensions/date-time/date-time.ts b/packages/editor/src/extensions/date-time/date-time.ts index 6de1d07fa..2c9ba8e7e 100644 --- a/packages/editor/src/extensions/date-time/date-time.ts +++ b/packages/editor/src/extensions/date-time/date-time.ts @@ -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({ @@ -61,7 +68,8 @@ export const DateTime = Extension.create({ addOptions() { return { dateFormat: "DD-MM-YYYY", - timeFormat: "12-hour" + timeFormat: "12-hour", + dayFormat: "short" }; }, @@ -98,6 +106,15 @@ export const DateTime = Extension.create({ 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({ }); } }), + 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; } diff --git a/packages/editor/src/extensions/task-list/component.tsx b/packages/editor/src/extensions/task-list/component.tsx index ffea131b9..5264c5c1e 100644 --- a/packages/editor/src/extensions/task-list/component.tsx +++ b/packages/editor/src/extensions/task-list/component.tsx @@ -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 }, diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index a389877b6..8274b646d 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -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; diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po index 76bd9c844..7ae54abaa 100644 --- a/packages/intl/locale/en.po +++ b/packages/intl/locale/en.po @@ -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" diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po index a3edcaa0f..84f1044af 100644 --- a/packages/intl/locale/pseudo-LOCALE.po +++ b/packages/intl/locale/pseudo-LOCALE.po @@ -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 "" diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts index c143b1962..706766fb7 100644 --- a/packages/intl/src/strings.ts +++ b/packages/intl/src/strings.ts @@ -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` };