From 58f7a206d988ecf57c2e6355a451c8f067d141bf Mon Sep 17 00:00:00 2001 From: ammarahm-ed Date: Thu, 1 Jun 2023 20:56:02 +0500 Subject: [PATCH] mobile: add settings --- .../components/list-items/notebook/index.js | 3 +- .../app/components/merge-conflicts/index.js | 4 +- .../app/components/note-history/index.js | 20 +- .../app/components/properties/date-meta.js | 4 +- .../mobile/app/components/properties/index.js | 16 +- .../components/sheets/restore-data/index.js | 10 +- .../app/components/ui/reminder-time/index.tsx | 10 +- apps/mobile/app/hooks/use-actions.js | 41 ++++ .../app/screens/editor/tiptap/use-editor.ts | 31 +-- apps/mobile/app/screens/notes/common.ts | 2 +- .../app/screens/settings/components.tsx | 7 +- .../app/screens/settings/date-format.jsx | 191 ++++++++++++++++++ .../app/screens/settings/settings-data.tsx | 30 +++ .../app/screens/settings/title-format.tsx | 61 ++++++ apps/mobile/app/utils/time/index.ts | 22 ++ packages/core/api/settings.js | 63 ++++++ packages/core/collections/notes.js | 55 +++-- packages/core/collections/reminders.js | 21 +- packages/core/common.js | 10 + packages/core/utils/date.js | 24 ++- 20 files changed, 559 insertions(+), 66 deletions(-) create mode 100644 apps/mobile/app/screens/settings/date-format.jsx create mode 100644 apps/mobile/app/screens/settings/title-format.tsx diff --git a/apps/mobile/app/components/list-items/notebook/index.js b/apps/mobile/app/components/list-items/notebook/index.js index 36738caeb..8c68e7b2f 100644 --- a/apps/mobile/app/components/list-items/notebook/index.js +++ b/apps/mobile/app/components/list-items/notebook/index.js @@ -31,6 +31,7 @@ import { Button } from "../../ui/button"; import { IconButton } from "../../ui/icon-button"; import Heading from "../../ui/typography/heading"; import Paragraph from "../../ui/typography/paragraph"; +import { getFormattedDate } from "../../../utils/time"; const showActionSheet = (item) => { Properties.present(item); @@ -163,7 +164,7 @@ export const NotebookItem = ({ marginRight: 6 }} > - {new Date(item[dateBy]).toDateString().substring(4)} + {getFormattedDate(item[dateBy], "date")} )} { {isCurrent ? "(This Device)" : "(Incoming)"} {"\n"} - {timeConverter(contentToKeep?.dateEdited)} + {getFormattedDate(contentToKeep?.dateEdited)} diff --git a/apps/mobile/app/components/note-history/index.js b/apps/mobile/app/components/note-history/index.js index c4800d61f..4745d5fbc 100644 --- a/apps/mobile/app/components/note-history/index.js +++ b/apps/mobile/app/components/note-history/index.js @@ -26,7 +26,7 @@ import { presentSheet } from "../../services/event-manager"; import { useThemeStore } from "../../stores/use-theme-store"; import { openLinkInBrowser } from "../../utils/functions"; import { SIZE } from "../../utils/size"; -import { timeConverter, timeSince } from "../../utils/time"; +import { getFormattedDate, timeSince } from "../../utils/time"; import DialogHeader from "../dialog/dialog-header"; import SheetProvider from "../sheet-provider"; import { PressableButton } from "../ui/pressable"; @@ -63,15 +63,15 @@ export default function NoteHistory({ note, fwdRef }) { }, []); const getDate = (start, end) => { - let _start = timeConverter(start); - let _end = timeConverter(end + 60000); - if (_start === _end) return _start; - let final = _end.lastIndexOf(","); - let part = _end.slice(0, final + 1); - if (_start.includes(part)) { - return _start + " —" + _end.replace(part, ""); - } - return _start + " — " + _end; + let _start_date = getFormattedDate(start, "date"); + let _end_date = getFormattedDate(end + 60000, "date"); + + let _start_time = getFormattedDate(start, "time"); + let _end_time = getFormattedDate(end + 60000, "time"); + + return `${_start_date} ${_start_time} - ${ + _end_date === _start_date ? " " : _end_date + " " + }${_end_time}`; }; const renderItem = useCallback( diff --git a/apps/mobile/app/components/properties/date-meta.js b/apps/mobile/app/components/properties/date-meta.js index 0c7841a2c..0ffce2b74 100644 --- a/apps/mobile/app/components/properties/date-meta.js +++ b/apps/mobile/app/components/properties/date-meta.js @@ -21,7 +21,7 @@ import React from "react"; import { View } from "react-native"; import { useThemeStore } from "../../stores/use-theme-store"; import { SIZE } from "../../utils/size"; -import { timeConverter } from "../../utils/time"; +import { getFormattedDate } from "../../utils/time"; import Paragraph from "../ui/typography/paragraph"; export const DateMeta = ({ item }) => { const colors = useThemeStore((state) => state.colors); @@ -66,7 +66,7 @@ export const DateMeta = ({ item }) => { {getNameFromKey(key)} - {timeConverter(item[key])} + {getFormattedDate(item[key], "date-time")} ); diff --git a/apps/mobile/app/components/properties/index.js b/apps/mobile/app/components/properties/index.js index 924d9bd12..9a4c8eca8 100644 --- a/apps/mobile/app/components/properties/index.js +++ b/apps/mobile/app/components/properties/index.js @@ -191,13 +191,25 @@ Properties.present = (item, buttons = [], isSheet) => { break; case "notebook": props[0] = db.notebooks.notebook(item.id).data; - props.push(["edit-notebook", "pin", "add-shortcut", "trash"]); + props.push([ + "edit-notebook", + "pin", + "add-shortcut", + "trash", + "default-notebook" + ]); break; case "topic": props[0] = db.notebooks .notebook(item.notebookId) .topics.topic(item.id)._topic; - props.push(["move-notes", "edit-topic", "add-shortcut", "trash"]); + props.push([ + "move-notes", + "edit-topic", + "add-shortcut", + "trash", + "default-topic" + ]); break; case "tag": props[0] = db.tags.tag(item.id); diff --git a/apps/mobile/app/components/sheets/restore-data/index.js b/apps/mobile/app/components/sheets/restore-data/index.js index 4201541e4..664776edc 100644 --- a/apps/mobile/app/components/sheets/restore-data/index.js +++ b/apps/mobile/app/components/sheets/restore-data/index.js @@ -21,21 +21,22 @@ import { EVENTS } from "@notesnook/core/common"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { ActivityIndicator, Platform, View } from "react-native"; import { FlatList } from "react-native-actions-sheet"; +import RNFetchBlob from "react-native-blob-util"; import DocumentPicker from "react-native-document-picker"; import * as ScopedStorage from "react-native-scoped-storage"; import { db } from "../../../common/database"; import storage from "../../../common/database/storage"; import { + ToastEvent, eSubscribeEvent, - eUnSubscribeEvent, - ToastEvent + eUnSubscribeEvent } from "../../../services/event-manager"; import SettingsService from "../../../services/settings"; import { initialize } from "../../../stores"; import { useThemeStore } from "../../../stores/use-theme-store"; import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events"; import { SIZE } from "../../../utils/size"; -import { timeConverter } from "../../../utils/time"; +import { getFormattedDate } from "../../../utils/time"; import { Dialog } from "../../dialog"; import DialogHeader from "../../dialog/dialog-header"; import { presentDialog } from "../../dialog/functions"; @@ -44,7 +45,6 @@ import { Button } from "../../ui/button"; import Seperator from "../../ui/seperator"; import SheetWrapper from "../../ui/sheet"; import Paragraph from "../../ui/typography/paragraph"; -import RNFetchBlob from "react-native-blob-util"; const RestoreDataSheet = () => { const [visible, setVisible] = useState(false); @@ -251,7 +251,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => { }} > - {timeConverter(item?.lastModified * 1)} + {getFormattedDate(item?.lastModified * 1)} {(item.filename || item.name).replace(".nnbackup", "")} diff --git a/apps/mobile/app/components/ui/reminder-time/index.tsx b/apps/mobile/app/components/ui/reminder-time/index.tsx index 751264e12..32e08ea22 100644 --- a/apps/mobile/app/components/ui/reminder-time/index.tsx +++ b/apps/mobile/app/components/ui/reminder-time/index.tsx @@ -16,17 +16,15 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { isReminderActive } from "@notesnook/core/collections/reminders"; import React from "react"; -import { - formatReminderTime, - isReminderActive -} from "@notesnook/core/collections/reminders"; import { ViewStyle } from "react-native"; import { Reminder } from "../../../services/notifications"; -import { Button, ButtonProps } from "../button"; import { useThemeStore } from "../../../stores/use-theme-store"; import { SIZE } from "../../../utils/size"; +import { getFormattedReminderTime } from "../../../utils/time"; +import { Button, ButtonProps } from "../button"; export const ReminderTime = ({ checkIsActive = true, @@ -42,7 +40,7 @@ export const ReminderTime = ({ } & ButtonProps) => { const colors = useThemeStore((state) => state.colors); const reminder = props.reminder; - const time = !reminder ? undefined : formatReminderTime(reminder); + const time = !reminder ? undefined : getFormattedReminderTime(reminder); const isTodayOrTomorrow = (time?.includes("Today") || time?.includes("Tomorrow")) && !time?.includes("Last"); diff --git a/apps/mobile/app/hooks/use-actions.js b/apps/mobile/app/hooks/use-actions.js index 42759c2c8..9d7e71d59 100644 --- a/apps/mobile/app/hooks/use-actions.js +++ b/apps/mobile/app/hooks/use-actions.js @@ -71,6 +71,9 @@ export const useActions = ({ close = () => null, item }) => { const user = useUserStore((state) => state.user); const [notifPinned, setNotifPinned] = useState(null); const alias = item.alias || item.title; + const [defaultNotebook, setDefaultNotebook] = useState( + db.settings.getDefaultNotebook() + ); const isPublished = item.type === "note" && db.monographs.isPublished(item.id); @@ -773,6 +776,44 @@ export const useActions = ({ close = () => null, item }) => { func: openHistory }, + { + id: "default-notebook", + title: + defaultNotebook?.id === item.id + ? "Remove as default notebook" + : "Set as default notebook", + hidden: item.type !== "notebook", + icon: "notebook", + func: async () => { + if (defaultNotebook?.id === item.id) { + await db.settings.setDefaultNotebook(); + setDefaultNotebook(); + } else { + await db.settings.setDefaultNotebook(item); + setDefaultNotebook(item); + } + }, + on: defaultNotebook?.id === item.id + }, + { + id: "default-topic", + title: + defaultNotebook?.id === item.id + ? "Remove as default topic" + : "Set as default topic", + hidden: item.type !== "topic", + icon: "bookmark", + func: async () => { + if (defaultNotebook?.id === item.id) { + await db.settings.setDefaultNotebook(); + setDefaultNotebook(); + } else { + await db.settings.setDefaultNotebook(item); + setDefaultNotebook(item); + } + }, + on: defaultNotebook?.id === item.id + }, { id: "disable-reminder", title: !item.disabled ? "Turn off reminder" : "Turn on reminder", diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor.ts b/apps/mobile/app/screens/editor/tiptap/use-editor.ts index 00fb89838..c2151e542 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor.ts @@ -38,8 +38,9 @@ import { useTagStore } from "../../../stores/use-tag-store"; import { ThemeStore, useThemeStore } from "../../../stores/use-theme-store"; import { eClearEditor, eOnLoadNote } from "../../../utils/events"; import { tabBarRef } from "../../../utils/global-refs"; -import { timeConverter } from "../../../utils/time"; +import { getFormattedDate } from "../../../utils/time"; import { NoteType } from "../../../utils/types"; +import { onNoteCreated } from "../../notes/common"; import Commands from "./commands"; import { Content, EditorState, Note, SavePayload } from "./types"; import { @@ -198,10 +199,7 @@ export const useEditor = ( sessionId: isContentInvalid(data) ? null : currentSessionHistoryId }; - if (title) { - noteData.title = title; - } - + noteData.title = title; if (data) { noteData.content = { data: data, @@ -213,12 +211,19 @@ export const useEditor = ( id = await db.notes?.add(noteData); if (!note && id) { currentNote.current = db.notes?.note(id).data as NoteType; - state.current?.onNoteCreated && state.current.onNoteCreated(id); + const defaultNotebook = db.settings?.getDefaultNotebook(); + if (!state.current.onNoteCreated && defaultNotebook) { + onNoteCreated(id, { + type: defaultNotebook.type, + id: defaultNotebook.id, + notebook: defaultNotebook.notebookId + }); + } else { + state.current?.onNoteCreated && state.current.onNoteCreated(id); + } + if (!noteData.title) { - postMessage( - EditorEvents.titleplaceholder, - currentNote.current.title - ); + postMessage(EditorEvents.title, currentNote.current.title); } } @@ -243,7 +248,7 @@ export const useEditor = ( } if (id && sessionIdRef.current === currentSessionId) { note = db.notes?.note(id)?.data as Note; - await commands.setStatus(timeConverter(note.dateEdited), "Saved"); + await commands.setStatus(getFormattedDate(note.dateEdited), "Saved"); lastContentChangeTime.current = note.dateEdited; @@ -373,7 +378,7 @@ export const useEditor = ( commands.setSessionId(nextSessionId); sessionIdRef.current = nextSessionId; currentNote.current = item as NoteType; - await commands.setStatus(timeConverter(item.dateEdited), "Saved"); + await commands.setStatus(getFormattedDate(item.dateEdited), "Saved"); await postMessage(EditorEvents.title, item.title); await postMessage( EditorEvents.html, @@ -460,7 +465,7 @@ export const useEditor = ( if (note.tags !== currentNote.current.tags) { await commands.setTags(note); } - await commands.setStatus(timeConverter(note.dateEdited), "Saved"); + await commands.setStatus(getFormattedDate(note.dateEdited), "Saved"); } lock.current = false; diff --git a/apps/mobile/app/screens/notes/common.ts b/apps/mobile/app/screens/notes/common.ts index 524ddbd5a..db03c109c 100644 --- a/apps/mobile/app/screens/notes/common.ts +++ b/apps/mobile/app/screens/notes/common.ts @@ -86,7 +86,7 @@ export const setOnFirstSave = ( }, 0); }; -async function onNoteCreated(id: string, params: FirstSaveData) { +export async function onNoteCreated(id: string, params: FirstSaveData) { if (!params) return; switch (params.type) { case "notebook": { diff --git a/apps/mobile/app/screens/settings/components.tsx b/apps/mobile/app/screens/settings/components.tsx index 3109e6c92..ee88a2e17 100644 --- a/apps/mobile/app/screens/settings/components.tsx +++ b/apps/mobile/app/screens/settings/components.tsx @@ -27,6 +27,8 @@ import SoundPicker from "./sound-picker"; import { Subscription } from "./subscription"; import { TrashIntervalSelector } from "./trash-interval-selector"; import { FontSelector } from "./font-selector"; +import { TitleFormat } from "./title-format"; +import { DateFormatSelector, TimeFormatSelector } from "./date-format"; export const components: { [name: string]: ReactElement } = { colorpicker: , homeselector: , @@ -37,5 +39,8 @@ export const components: { [name: string]: ReactElement } = { "sound-picker": , licenses: , "trash-interval-selector": , - "font-selector": + "font-selector": , + "title-format": , + "date-format-selector": , + "time-format-selector": }; diff --git a/apps/mobile/app/screens/settings/date-format.jsx b/apps/mobile/app/screens/settings/date-format.jsx new file mode 100644 index 000000000..0f876d80b --- /dev/null +++ b/apps/mobile/app/screens/settings/date-format.jsx @@ -0,0 +1,191 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import dayjs from "dayjs"; +import React, { useRef, useState } from "react"; +import { View } from "react-native"; +import Menu, { MenuItem } from "react-native-reanimated-material-menu"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import { db } from "../../common/database"; +import { PressableButton } from "../../components/ui/pressable"; +import Paragraph from "../../components/ui/typography/paragraph"; +import { useThemeStore } from "../../stores/use-theme-store"; +import { SIZE } from "../../utils/size"; +import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common"; + +export const DateFormatSelector = () => { + const colors = useThemeStore((state) => state.colors); + const menuRef = useRef(); + const [width, setWidth] = useState(0); + const [dateFormat, setDateFormat] = useState(db.settings.getDateFormat()); + const onChange = (item) => { + menuRef.current?.hide(); + db.settings.setDateFormat(item); + setDateFormat(item); + }; + + return ( + { + setWidth(event.nativeEvent.layout.width); + }} + style={{ + width: "100%" + }} + > + { + menuRef.current?.hide(); + }} + anchor={ + { + menuRef.current?.show(); + }} + type="grayBg" + customStyle={{ + flexDirection: "row", + alignItems: "center", + marginTop: 10, + width: "100%", + justifyContent: "space-between", + padding: 12 + }} + > + + {dateFormat} ({dayjs().format(dateFormat)}) + + + + } + > + {DATE_FORMATS.map((item) => ( + { + onChange(item); + }} + style={{ + backgroundColor: dateFormat === item ? colors.nav : "transparent", + width: "100%", + maxWidth: width + }} + textStyle={{ + fontSize: SIZE.md, + color: dateFormat === item ? colors.accent : colors.pri + }} + > + {item} ({dayjs().format(item)}) + + ))} + + + ); +}; + +export const TimeFormatSelector = () => { + const colors = useThemeStore((state) => state.colors); + const menuRef = useRef(); + const [width, setWidth] = useState(0); + const [timeFormat, setTimeFormat] = useState(db.settings.getTimeFormat()); + const onChange = (item) => { + menuRef.current?.hide(); + db.settings.setTimeFormat(item); + setTimeFormat(item); + }; + + const TimeFormats = { + "12-hour": "hh:mm A", + "24-hour": "HH:mm" + }; + + return ( + { + setWidth(event.nativeEvent.layout.width); + }} + style={{ + width: "100%" + }} + > + { + menuRef.current?.hide(); + }} + anchor={ + { + menuRef.current?.show(); + }} + type="grayBg" + customStyle={{ + flexDirection: "row", + alignItems: "center", + marginTop: 10, + width: "100%", + justifyContent: "space-between", + padding: 12 + }} + > + + {timeFormat} ({dayjs().format(TimeFormats[timeFormat])}) + + + + } + > + {TIME_FORMATS.map((item) => ( + { + onChange(item); + }} + style={{ + backgroundColor: timeFormat === item ? colors.nav : "transparent", + width: "100%", + maxWidth: width + }} + textStyle={{ + fontSize: SIZE.md, + color: timeFormat === item ? colors.accent : colors.pri + }} + > + {item} ({dayjs().format(TimeFormats[item])}) + + ))} + + + ); +}; diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx index 9080d3dc5..728790136 100644 --- a/apps/mobile/app/screens/settings/settings-data.tsx +++ b/apps/mobile/app/screens/settings/settings-data.tsx @@ -524,6 +524,20 @@ export const settingsGroups: SettingSection[] = [ description: "Default screen to open on app startup", component: "homeselector" }, + { + id: "date-format", + name: "Date format", + description: "Set the format for date used across the app", + type: "component", + component: "date-format-selector" + }, + { + id: "time-format", + name: "Time format", + description: "Set the format for time used across the app", + type: "component", + component: "time-format-selector" + }, { id: "clear-trash-interval", type: "component", @@ -531,6 +545,15 @@ export const settingsGroups: SettingSection[] = [ description: "Select the duration after which trash items will be cleared", component: "trash-interval-selector" + }, + { + id: "default-notebook", + name: "Clear default notebook", + description: "Clear the default notebook for new notes", + modifer: () => { + db.settings?.setDefaultNotebook(undefined); + }, + hidden: () => !db.settings?.getDefaultNotebook() } ] }, @@ -588,6 +611,13 @@ export const settingsGroups: SettingSection[] = [ icon: "format-font", property: "defaultFontFamily", component: "font-selector" + }, + { + id: "title-format", + name: "Title format", + component: "title-format", + description: "Customize the formatting for new note title", + type: "component" } ] } diff --git a/apps/mobile/app/screens/settings/title-format.tsx b/apps/mobile/app/screens/settings/title-format.tsx new file mode 100644 index 000000000..e5cf056be --- /dev/null +++ b/apps/mobile/app/screens/settings/title-format.tsx @@ -0,0 +1,61 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { useRef, useState } from "react"; +import { db } from "../../common/database"; +import Input from "../../components/ui/input"; +import React from "react"; +import { TextInput } from "react-native"; +import Paragraph from "../../components/ui/typography/paragraph"; +import { useThemeStore } from "../../stores/use-theme-store"; +import { SIZE } from "../../utils/size"; + +export const TitleFormat = () => { + const [titleFormat] = useState(db.settings?.getTitleFormat()); + const inputRef = useRef(); + const colors = useThemeStore((state) => state.colors); + + return ( + <> + { + db.settings?.setTitleFormat(e.nativeEvent.text); + }} + onChangeText={(text) => { + db.settings?.setTitleFormat(text); + }} + containerStyle={{ marginTop: 6 }} + onLayout={() => { + inputRef?.current?.setNativeProps({ + text: titleFormat + }); + }} + defaultValue={titleFormat} + /> + + + Use the following key to format the title:{"\n"} + {"\n"} + $date$: Current date{"\n"} + $time$: Current time{"\n"} + $count$: Number of untitled notes + 1{"\n"} + $headline$: Use starting line of the note as title{"\n"} + + + ); +}; diff --git a/apps/mobile/app/utils/time/index.ts b/apps/mobile/app/utils/time/index.ts index 477a2f106..5f2802d61 100644 --- a/apps/mobile/app/utils/time/index.ts +++ b/apps/mobile/app/utils/time/index.ts @@ -17,6 +17,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { formatDate } from "@notesnook/core/utils/date"; +import { db } from "../../common/database"; +import { formatReminderTime } from "@notesnook/core/collections/reminders"; + export const sleep = (duration: number) => new Promise((resolve) => setTimeout(() => resolve(true), duration)); @@ -107,3 +111,21 @@ export const timeConverter = (timestamp: number | undefined | null) => { return time; }; + +export function getFormattedDate( + date: any, + type: "time" | "date-time" | "date" = "date-time" +) { + return formatDate(date, { + dateFormat: db.settings?.getDateFormat() as string, + timeFormat: db.settings?.getTimeFormat() as string, + type: type + }); +} + +export function getFormattedReminderTime(reminder: any, short = false) { + return formatReminderTime(reminder, short, { + dateFormat: db.settings?.getDateFormat() as string, + timeFormat: db.settings?.getTimeFormat() as string + }); +} diff --git a/packages/core/api/settings.js b/packages/core/api/settings.js index 5681114b0..378875026 100644 --- a/packages/core/api/settings.js +++ b/packages/core/api/settings.js @@ -140,6 +140,66 @@ class Settings { return this._settings.trashCleanupInterval || 7; } + /** + * + * @param {{type: "notebook" | "topic", id: string, notebookId?: string} | undefined} item + */ + async setDefaultNotebook(item) { + this._settings.defaultNotebook = !item + ? undefined + : { + id: item.id, + type: item.type, + notebookId: item.type === "topic" ? item.notebookId : undefined + }; + await this._saveSettings(); + } + /** + * + * @returns {Notebook | Topic | undefined} + */ + getDefaultNotebook() { + const notebook = this._settings.defaultNotebook; + if (!notebook) return; + if (notebook.type === "topic") { + return this._db.notebooks + .notebook(notebook.notebookId) + .topics.topic(notebook.id)._topic; + } else { + return this._db.notebooks.notebook(notebook.id).data; + } + } + + async setTitleFormat(format) { + this._settings.titleFormat = format || "Note $date$ $time$"; + await this._saveSettings(); + } + + getTitleFormat() { + return this._settings.titleFormat; + } + + getDateFormat() { + return this._settings.dateFormat; + } + + async setDateFormat(format) { + this._settings.dateFormat = format || "DD-MM-YYYY"; + await this._saveSettings(); + } + /** + * + * @returns {"12-hour" | "24-hour"} + */ + getTimeFormat() { + return this._settings.timeFormat; + } + + async setTimeFormat(format) { + this._settings.timeFormat = format || "12-hour"; + await this._saveSettings(); + } + _initSettings(settings) { this._settings = { type: "settings", @@ -150,6 +210,9 @@ class Settings { dateModified: 0, dateCreated: 0, trashCleanupInterval: 7, + titleFormat: "Note $date$ $time$", + timeFormat: "12-hour", + dateFormat: "DD-MM-YYYY", ...(settings || {}) }; } diff --git a/packages/core/collections/notes.js b/packages/core/collections/notes.js index 07df73bc4..713fdca06 100644 --- a/packages/core/collections/notes.js +++ b/packages/core/collections/notes.js @@ -23,6 +23,12 @@ import getId from "../utils/id"; import { getContentFromData } from "../content-types"; import qclone from "qclone"; import { deleteItem, findById } from "../utils/array"; +import { formatDate } from "../utils/date"; + +const DATE_REGEX = /\$date\$/gm; +const COUNT_REGEX = /\$count\$/gm; +const TIME_REGEX = /\$time\$/gm; +const HEADLINE_REGEX = /\$headline\$/gm; /** * @typedef {{ id: string, topic?: string, rebuildCache?: boolean }} NotebookReference @@ -112,7 +118,7 @@ export default class Notes extends Collection { }); } - const noteTitle = getNoteTitle(note, oldNote); + const noteTitle = this._getNoteTitle(note, oldNote, note.headline); if (oldNote && oldNote.title !== noteTitle) note.dateEdited = Date.now(); note = { @@ -408,6 +414,41 @@ export default class Notes extends Collection { await this._collection.updateItem(note); } } + + formatTitle(title, headline) { + const date = formatDate(Date.now(), { + dateFormat: this._db.settings.getDateFormat(), + timeFormat: this._db.settings.getTimeFormat(), + type: "date" + }); + + const time = formatDate(Date.now(), { + dateFormat: this._db.settings.getDateFormat(), + timeFormat: this._db.settings.getTimeFormat(), + type: "time" + }); + + return title + .replace(NEWLINE_STRIP_REGEX, " ") + .replace(DATE_REGEX, date) + .replace(TIME_REGEX, time) + .replace(HEADLINE_REGEX, headline) + .replace(COUNT_REGEX, this.all.length); + } + + _getNoteTitle(note, oldNote, headline) { + if (note.title && note.title.trim().length > 0) { + return note.title.replace(NEWLINE_STRIP_REGEX, " "); + } else if ( + oldNote && + oldNote.title && + oldNote.title.trim().length > 0 && + (note.title === undefined || note.title === null) + ) { + return oldNote.title.replace(NEWLINE_STRIP_REGEX, " "); + } + return this.formatTitle(this._db.settings.getTitleFormat(), headline); + } } function getNoteHeadline(note, content) { @@ -416,18 +457,6 @@ function getNoteHeadline(note, content) { } const NEWLINE_STRIP_REGEX = /[\r\n\t\v]+/gm; -function getNoteTitle(note, oldNote) { - if (note.title && note.title.trim().length > 0) { - return note.title.replace(NEWLINE_STRIP_REGEX, " "); - } else if (oldNote && oldNote.title && oldNote.title.trim().length > 0) { - return oldNote.title.replace(NEWLINE_STRIP_REGEX, " "); - } - - return `Note ${new Date().toLocaleString(undefined, { - dateStyle: "short", - timeStyle: "short" - })}`; -} class NoteIdCache { /** diff --git a/packages/core/collections/reminders.js b/packages/core/collections/reminders.js index a02f05d51..2ad19c7fc 100644 --- a/packages/core/collections/reminders.js +++ b/packages/core/collections/reminders.js @@ -124,7 +124,14 @@ export default class Reminders extends Collection { /** * @param {Reminder} reminder */ -export function formatReminderTime(reminder, short = false) { +export function formatReminderTime( + reminder, + short = false, + options = { + timeFormat: "12-hour", + dateFormat: "DD-MM-YYYY" + } +) { const { date } = reminder; let time = date; let tag = ""; @@ -133,7 +140,9 @@ export function formatReminderTime(reminder, short = false) { if (reminder.mode === "permanent") return `Ongoing`; if (reminder.snoozeUntil && reminder.snoozeUntil > Date.now()) { - return `Snoozed until ${dayjs(reminder.snoozeUntil).format("hh:mm A")}`; + return `Snoozed until ${dayjs(reminder.snoozeUntil).format( + options.timeFormat + )}`; } if (reminder.mode === "repeat") { @@ -142,17 +151,17 @@ export function formatReminderTime(reminder, short = false) { if (dayjs(time).isTomorrow()) { tag = "Upcoming"; - text = `Tomorrow, ${dayjs(time).format("hh:mm A")}`; + text = `Tomorrow, ${dayjs(time).format(options.timeFormat)}`; } else if (dayjs(time).isYesterday()) { tag = "Last"; - text = `Yesterday, ${dayjs(time).format("hh:mm A")}`; + text = `Yesterday, ${dayjs(time).format(options.timeFormat)}`; } else { const isPast = dayjs(time).isSameOrBefore(dayjs()); tag = isPast ? "Last" : "Upcoming"; if (dayjs(time).isToday()) { - text = `Today, ${dayjs(time).format("hh:mm A")}`; + text = `Today, ${dayjs(time).format(options.timeFormat)}`; } else { - text = dayjs(time).format(`ddd, YYYY-MM-DD hh:mm A`); + text = dayjs(time).format(options.dateFormat); } } diff --git a/packages/core/common.js b/packages/core/common.js index f493e2815..c7c002706 100644 --- a/packages/core/common.js +++ b/packages/core/common.js @@ -106,4 +106,14 @@ export const EVENTS = { systemTimeInvalid: "system:invalidTime" }; +export const DATE_FORMATS = [ + "DD-MM-YYYY", + "YYYY-DD-MM", + "DD/MM/YYYY", + "YYYY/DD/MM", + "MMM D, YYYY" +]; + +export const TIME_FORMATS = ["12-hour", "24-hour"]; + export const CURRENT_DATABASE_VERSION = 5.8; diff --git a/packages/core/utils/date.js b/packages/core/utils/date.js index 42f1a72c5..a8619ec7b 100644 --- a/packages/core/utils/date.js +++ b/packages/core/utils/date.js @@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import dayjs from "dayjs"; + export function getWeekGroupFromTimestamp(timestamp) { const date = new Date(timestamp); const { start, end } = getWeek(date); @@ -64,17 +66,31 @@ function getWeek(date) { /** * * @param {number} date - * @param {Intl.DateTimeFormatOptions} options + * @param {{dateFormat: string, timeFormat: string, type: "date-time" | "time" | "date"}} options * @returns */ export function formatDate( date, options = { - dateStyle: "medium", - timeStyle: "short" + dateFormat: "DD-MM-YYYY", + timeFormat: "12-hour", + type: "date-time" } ) { - return new Date(date).toLocaleString(undefined, options); + switch (options.type) { + case "date-time": + return dayjs(date).format( + `${options.dateFormat} ${ + options.timeFormat === "12-hour" ? "h:mm A" : "HH:mm" + }` + ); + case "time": + return dayjs(date).format( + `${options.timeFormat === "12-hour" ? "h:mm A" : "HH:mm"}` + ); + case "date": + return dayjs(date).format(`${options.dateFormat}`); + } } export const MONTHS_FULL = [