mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
mobile: add settings
This commit is contained in:
committed by
Abdullah Atta
parent
25bf4fee82
commit
58f7a206d9
@@ -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")}
|
||||
</Paragraph>
|
||||
)}
|
||||
<Paragraph
|
||||
|
||||
@@ -37,7 +37,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { dHeight } from "../../utils";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep, timeConverter } from "../../utils/time";
|
||||
import { getFormattedDate, sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogButtons from "../dialog/dialog-buttons";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
@@ -166,7 +166,7 @@ const MergeConflicts = () => {
|
||||
{isCurrent ? "(This Device)" : "(Incoming)"}
|
||||
</Text>
|
||||
{"\n"}
|
||||
{timeConverter(contentToKeep?.dateEdited)}
|
||||
{getFormattedDate(contentToKeep?.dateEdited)}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)}
|
||||
</Paragraph>
|
||||
<Paragraph size={SIZE.xs} color={colors.icon}>
|
||||
{timeConverter(item[key])}
|
||||
{getFormattedDate(item[key], "date-time")}
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }) => {
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.sm} style={{ width: "100%", maxWidth: "100%" }}>
|
||||
{timeConverter(item?.lastModified * 1)}
|
||||
{getFormattedDate(item?.lastModified * 1)}
|
||||
</Paragraph>
|
||||
<Paragraph size={SIZE.xs}>
|
||||
{(item.filename || item.name).replace(".nnbackup", "")}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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: <AccentColorPicker />,
|
||||
homeselector: <HomagePageSelector />,
|
||||
@@ -37,5 +39,8 @@ export const components: { [name: string]: ReactElement } = {
|
||||
"sound-picker": <SoundPicker />,
|
||||
licenses: <Licenses />,
|
||||
"trash-interval-selector": <TrashIntervalSelector />,
|
||||
"font-selector": <FontSelector />
|
||||
"font-selector": <FontSelector />,
|
||||
"title-format": <TitleFormat />,
|
||||
"date-format-selector": <DateFormatSelector />,
|
||||
"time-format-selector": <TimeFormatSelector />
|
||||
};
|
||||
|
||||
191
apps/mobile/app/screens/settings/date-format.jsx
Normal file
191
apps/mobile/app/screens/settings/date-format.jsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{dateFormat} ({dayjs().format(dateFormat)})
|
||||
</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{DATE_FORMATS.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onPress={async () => {
|
||||
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)})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{timeFormat} ({dayjs().format(TimeFormats[timeFormat])})
|
||||
</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{TIME_FORMATS.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onPress={async () => {
|
||||
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])})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
61
apps/mobile/app/screens/settings/title-format.tsx
Normal file
61
apps/mobile/app/screens/settings/title-format.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<TextInput>();
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
onSubmit={(e) => {
|
||||
db.settings?.setTitleFormat(e.nativeEvent.text);
|
||||
}}
|
||||
onChangeText={(text) => {
|
||||
db.settings?.setTitleFormat(text);
|
||||
}}
|
||||
containerStyle={{ marginTop: 6 }}
|
||||
onLayout={() => {
|
||||
inputRef?.current?.setNativeProps({
|
||||
text: titleFormat
|
||||
});
|
||||
}}
|
||||
defaultValue={titleFormat}
|
||||
/>
|
||||
|
||||
<Paragraph style={{ marginTop: 2 }} color={colors.icon} size={SIZE.xs}>
|
||||
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"}
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -17,6 +17,10 @@ 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 { 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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 || {})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,6 +17,8 @@ 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 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 = [
|
||||
|
||||
Reference in New Issue
Block a user