diff --git a/apps/mobile/app/common/database/index.js b/apps/mobile/app/common/database/index.js
index 989d31574..ad37733fd 100644
--- a/apps/mobile/app/common/database/index.js
+++ b/apps/mobile/app/common/database/index.js
@@ -16,8 +16,7 @@ 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 Database from "@notesnook/core/api/index";
+import { database } from "@notesnook/common";
import { initalize, logger as dbLogger } from "@notesnook/core/logger";
import { Platform } from "react-native";
import { MMKVLoader } from "react-native-mmkv-storage";
@@ -30,13 +29,8 @@ import * as Gzip from "react-native-gzip";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
-initalize(new KV(LoggerStorage), true);
-export const DatabaseLogger = dbLogger;
-/**
- * @type {import("@notesnook/core/api/index").default}
- */
-export var db = new Database(
+database.setup(
Storage,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem,
@@ -46,7 +40,7 @@ export var db = new Database(
}
);
-db.host(
+database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
@@ -68,3 +62,8 @@ db.host(
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
+
+initalize(new KV(LoggerStorage), true);
+
+export const db = database;
+export const DatabaseLogger = dbLogger;
diff --git a/apps/mobile/app/common/filesystem/attach-file.ts b/apps/mobile/app/common/filesystem/attach-file.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/mobile/app/components/attachments/actions.js b/apps/mobile/app/components/attachments/actions.js
index e6398dda6..b3b0c2c86 100644
--- a/apps/mobile/app/components/attachments/actions.js
+++ b/apps/mobile/app/components/attachments/actions.js
@@ -34,7 +34,6 @@ import {
import PremiumService from "../../services/premium";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
-import { formatBytes } from "../../utils";
import { eCloseAttachmentDialog, eCloseSheet } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
@@ -47,6 +46,7 @@ import { Notice } from "../ui/notice";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
+import { formatBytes } from "@notesnook/common";
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const colors = useThemeStore((state) => state.colors);
diff --git a/apps/mobile/app/components/attachments/attachment-item.js b/apps/mobile/app/components/attachments/attachment-item.js
index 339606af0..2eec00a08 100644
--- a/apps/mobile/app/components/attachments/attachment-item.js
+++ b/apps/mobile/app/components/attachments/attachment-item.js
@@ -23,7 +23,7 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useThemeStore } from "../../stores/use-theme-store";
-import { formatBytes } from "../../utils";
+import { formatBytes } from "@notesnook/common";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
diff --git a/apps/mobile/app/components/container/floating-button.js b/apps/mobile/app/components/container/floating-button.js
index 2bbc5da12..4e6c42898 100644
--- a/apps/mobile/app/components/container/floating-button.js
+++ b/apps/mobile/app/components/container/floating-button.js
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import React, { useEffect } from "react";
+import React, { useCallback, useEffect } from "react";
import { Keyboard, Platform, View } from "react-native";
import Animated, {
Easing,
@@ -30,10 +30,10 @@ import { notesnook } from "../../../e2e/test.ids";
import { editorState } from "../../screens/editor/tiptap/utils";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store";
-import { getElevation, showTooltip, TOOLTIP_POSITIONS } from "../../utils";
-import { normalize, SIZE } from "../../utils/size";
+import { getElevationStyle } from "../../utils/elevation";
+import { SIZE, normalize } from "../../utils/size";
+import NativeTooltip from "../../utils/tooltip";
import { PressableButton } from "../ui/pressable";
-import { useCallback } from "react";
export const FloatingButton = ({
title,
@@ -116,11 +116,11 @@ export const FloatingButton = ({
accentColor={color || "accent"}
accentText="light"
customStyle={{
- ...getElevation(5),
+ ...getElevationStyle(5),
borderRadius: 100
}}
onLongPress={(event) => {
- showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
+ NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}}
onPress={onPress}
>
diff --git a/apps/mobile/app/components/dialog/dialog-container.js b/apps/mobile/app/components/dialog/dialog-container.js
index 9cdc15c91..476dbb417 100644
--- a/apps/mobile/app/components/dialog/dialog-container.js
+++ b/apps/mobile/app/components/dialog/dialog-container.js
@@ -21,7 +21,7 @@ import React from "react";
import { View } from "react-native";
import { DDS } from "../../services/device-detection";
import { useThemeStore } from "../../stores/use-theme-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
const DialogContainer = ({ width, height, ...restProps }) => {
const colors = useThemeStore((state) => state.colors);
@@ -30,7 +30,7 @@ const DialogContainer = ({ width, height, ...restProps }) => {
{
};
const style = {
- ...getElevation(5),
+ ...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
borderRadius: 5,
diff --git a/apps/mobile/app/components/dialogs/jump-to-section/index.js b/apps/mobile/app/components/dialogs/jump-to-section/index.js
index 95613f906..e33c7bc8c 100644
--- a/apps/mobile/app/components/dialogs/jump-to-section/index.js
+++ b/apps/mobile/app/components/dialogs/jump-to-section/index.js
@@ -26,7 +26,7 @@ import {
} from "../../../services/event-manager";
import { useMessageStore } from "../../../stores/use-message-store";
import { useThemeStore } from "../../../stores/use-theme-store";
-import { getElevation } from "../../../utils";
+import { getElevationStyle } from "../../../utils/elevation";
import {
eCloseJumpToDialog,
eOpenJumpToDialog,
@@ -124,7 +124,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
>
{
{
onRequestClose={() => {
menuRef.current?.hide();
}}
- anchor={
+ button={
{
menuRef.current?.show();
diff --git a/apps/mobile/app/components/intro/index.js b/apps/mobile/app/components/intro/index.js
index c502d617a..ccad4c092 100644
--- a/apps/mobile/app/components/intro/index.js
+++ b/apps/mobile/app/components/intro/index.js
@@ -29,7 +29,7 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
@@ -198,7 +198,7 @@ const Intro = ({ navigation }) => {
style={{
paddingHorizontal: 24,
alignSelf: "center",
- ...getElevation(5),
+ ...getElevationStyle(5),
borderRadius: 100
}}
fontSize={SIZE.md}
diff --git a/apps/mobile/app/components/intro/welcome.js b/apps/mobile/app/components/intro/welcome.js
index 4c2caa242..707daadca 100644
--- a/apps/mobile/app/components/intro/welcome.js
+++ b/apps/mobile/app/components/intro/welcome.js
@@ -24,7 +24,7 @@ import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SettingsService from "../../services/settings";
import { useThemeStore } from "../../stores/use-theme-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import {
eCloseLoading,
eOpenLoading,
@@ -91,7 +91,7 @@ export const WelcomeNotice = () => {
paddingHorizontal: 24,
alignSelf: "center",
borderRadius: 100,
- ...getElevation(5),
+ ...getElevationStyle(5),
marginTop: 30
}}
type="accent"
diff --git a/apps/mobile/app/components/launcher/index.js b/apps/mobile/app/components/launcher/index.js
index 7a6fc46b6..8d62be8a3 100644
--- a/apps/mobile/app/components/launcher/index.js
+++ b/apps/mobile/app/components/launcher/index.js
@@ -37,9 +37,9 @@ import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
-import { AndroidModule } from "../../utils";
import { eOpenAnnouncementDialog } from "../../utils/events";
import { getGithubVersion } from "../../utils/github-version";
+import { NotesnookModule } from "../../utils/notesnook-module";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import Migrate from "../sheets/migrate";
@@ -175,7 +175,7 @@ const Launcher = React.memo(
const onUnlockBiometrics = useCallback(async () => {
if (!(await BiometricService.isBiometryAvailable())) return;
if (Platform.OS === "android") {
- const activityName = await AndroidModule.getActivityName();
+ const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
}
diff --git a/apps/mobile/app/components/list-items/headers/notebook-header.js b/apps/mobile/app/components/list-items/headers/notebook-header.js
index 97343e950..a797c49ff 100644
--- a/apps/mobile/app/components/list-items/headers/notebook-header.js
+++ b/apps/mobile/app/components/list-items/headers/notebook-header.js
@@ -23,7 +23,7 @@ import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useMenuStore } from "../../../stores/use-menu-store";
import { ToastEvent } from "../../../services/event-manager";
-import { getTotalNotes } from "../../../utils";
+import { getTotalNotes } from "@notesnook/common";
import { db } from "../../../common/database";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
diff --git a/apps/mobile/app/components/list-items/note/wrapper.js b/apps/mobile/app/components/list-items/note/wrapper.js
index e005de3b4..21df05dcf 100644
--- a/apps/mobile/app/components/list-items/note/wrapper.js
+++ b/apps/mobile/app/components/list-items/note/wrapper.js
@@ -30,7 +30,6 @@ import {
} from "../../../services/event-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
-import { history } from "../../../utils";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { presentDialog } from "../../dialog/functions";
@@ -59,12 +58,14 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
return;
}
}
+ const { selectedItemsList, selectionMode, clearSelection } =
+ useSelectionStore.getState();
- if (history.selectedItemsList.length > 0 && history.selectionMode) {
+ if (selectedItemsList.length > 0 && selectionMode) {
setSelectedItem && setSelectedItem(_note);
return;
} else {
- history.selectedItemsList = [];
+ clearSelection();
}
if (_note.conflicted) {
diff --git a/apps/mobile/app/components/list-items/notebook/index.js b/apps/mobile/app/components/list-items/notebook/index.js
index 165621d0a..1fe4abd96 100644
--- a/apps/mobile/app/components/list-items/notebook/index.js
+++ b/apps/mobile/app/components/list-items/notebook/index.js
@@ -22,23 +22,23 @@ import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { TopicNotes } from "../../../screens/notes/topic-notes";
+import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
-import { history } from "../../../utils";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
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";
+import { getFormattedDate } from "@notesnook/common";
const showActionSheet = (item) => {
Properties.present(item);
};
const navigateToTopic = (topic) => {
- if (history.selectedItemsList.length > 0) return;
+ if (useSelectionStore.getState().selectedItemsList.length > 0) return;
TopicNotes.navigate(topic, true);
};
diff --git a/apps/mobile/app/components/list-items/notebook/wrapper.js b/apps/mobile/app/components/list-items/notebook/wrapper.js
index a1873a1b1..28bab39ba 100644
--- a/apps/mobile/app/components/list-items/notebook/wrapper.js
+++ b/apps/mobile/app/components/list-items/notebook/wrapper.js
@@ -24,7 +24,6 @@ import { ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
-import { history } from "../../../utils";
import { db } from "../../../common/database";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
@@ -49,11 +48,13 @@ const navigateToNotebook = (item, canGoBack) => {
export const openNotebookTopic = (item) => {
const isTrash = item.type === "trash";
- if (history.selectedItemsList.length > 0 && history.selectionMode) {
- useSelectionStore.getState().setSelectedItem(item);
+ const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
+ useSelectionStore.getState();
+ if (selectedItemsList.length > 0 && selectionMode) {
+ setSelectedItem(item);
return;
} else {
- history.selectedItemsList = [];
+ clearSelection();
}
if (isTrash) {
diff --git a/apps/mobile/app/components/list/index.js b/apps/mobile/app/components/list/index.js
index 28c3fc7bf..b1d95d03c 100644
--- a/apps/mobile/app/components/list/index.js
+++ b/apps/mobile/app/components/list/index.js
@@ -36,7 +36,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import TagItem from "../list-items/tag";
import { Empty } from "./empty";
-import { getTotalNotes } from "../../utils";
+import { getTotalNotes } from "@notesnook/common";
import { useSettingStore } from "../../stores/use-setting-store";
import ReminderItem from "../list-items/reminder";
diff --git a/apps/mobile/app/components/merge-conflicts/index.js b/apps/mobile/app/components/merge-conflicts/index.js
index ec30f74a6..4a9c30950 100644
--- a/apps/mobile/app/components/merge-conflicts/index.js
+++ b/apps/mobile/app/components/merge-conflicts/index.js
@@ -34,10 +34,9 @@ import {
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
-import { dHeight } from "../../utils";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
-import { getFormattedDate, sleep } from "../../utils/time";
+import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import DialogButtons from "../dialog/dialog-buttons";
import DialogContainer from "../dialog/dialog-container";
@@ -46,6 +45,8 @@ import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
+import { useSettingStore } from "../../stores/use-setting-store";
+import { getFormattedDate } from "@notesnook/common";
const MergeConflicts = () => {
const colors = useThemeStore((state) => state.colors);
@@ -57,6 +58,7 @@ const MergeConflicts = () => {
const content = useRef(null);
const isKeepingConflicted = !keep?.conflicted;
const isKeeping = !!keep;
+ const { height } = useSettingStore((state) => state.dimensions);
const applyChanges = async () => {
let _content = keep;
@@ -296,7 +298,7 @@ const MergeConflicts = () => {
{
{getDate(item.dateCreated, item.dateModified)}
- {timeSince(item.dateModified)}
+ {getTimeAgo(item.dateModified)}
),
diff --git a/apps/mobile/app/components/premium/component.js b/apps/mobile/app/components/premium/component.js
index bbe25fc23..04d559924 100644
--- a/apps/mobile/app/components/premium/component.js
+++ b/apps/mobile/app/components/premium/component.js
@@ -26,7 +26,7 @@ import { DDS } from "../../services/device-detection";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import {
eClosePremiumDialog,
eCloseSheet,
@@ -271,7 +271,7 @@ export const Component = ({ close, promo }) => {
position: "absolute",
borderRadius: 100,
bottom: 30,
- ...getElevation(10)
+ ...getElevationStyle(10)
}}
/>
) : null}
diff --git a/apps/mobile/app/components/premium/premium-toast.js b/apps/mobile/app/components/premium/premium-toast.js
index fce278809..1900feada 100644
--- a/apps/mobile/app/components/premium/premium-toast.js
+++ b/apps/mobile/app/components/premium/premium-toast.js
@@ -28,7 +28,7 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import {
eCloseActionSheet,
eCloseSheet,
@@ -94,7 +94,7 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
position: "absolute",
backgroundColor: colors.nav,
zIndex: 999,
- ...getElevation(20),
+ ...getElevationStyle(20),
padding: 12,
borderRadius: 10,
flexDirection: "row",
diff --git a/apps/mobile/app/components/properties/date-meta.js b/apps/mobile/app/components/properties/date-meta.js
index 0ffce2b74..7e0c1f9c8 100644
--- a/apps/mobile/app/components/properties/date-meta.js
+++ b/apps/mobile/app/components/properties/date-meta.js
@@ -21,8 +21,8 @@ import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
-import { getFormattedDate } from "../../utils/time";
import Paragraph from "../ui/typography/paragraph";
+import { getFormattedDate } from "@notesnook/common";
export const DateMeta = ({ item }) => {
const colors = useThemeStore((state) => state.colors);
diff --git a/apps/mobile/app/components/sheets/change-email/index.tsx b/apps/mobile/app/components/sheets/change-email/index.tsx
index bc0a6ddce..9ceb67725 100644
--- a/apps/mobile/app/components/sheets/change-email/index.tsx
+++ b/apps/mobile/app/components/sheets/change-email/index.tsx
@@ -41,11 +41,7 @@ enum EmailChangeSteps {
changeEmail
}
-export const ChangeEmail = ({
- actionSheetRef,
- close,
- update
-}: ChangeEmailProps) => {
+export const ChangeEmail = ({ close }: ChangeEmailProps) => {
const [step, setStep] = useState(EmailChangeSteps.verify);
const emailChangeData = useRef<{
email?: string;
diff --git a/apps/mobile/app/components/sheets/export-notes/index.js b/apps/mobile/app/components/sheets/export-notes/index.js
index 2268d8433..6d30cfa42 100644
--- a/apps/mobile/app/components/sheets/export-notes/index.js
+++ b/apps/mobile/app/components/sheets/export-notes/index.js
@@ -29,7 +29,7 @@ import Exporter from "../../../services/exporter";
import PremiumService from "../../../services/premium";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useUserStore } from "../../../stores/use-user-store";
-import { getElevation } from "../../../utils";
+import { getElevationStyle } from "../../../utils/elevation";
import { ph, pv, SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import DialogHeader from "../../dialog/dialog-header";
@@ -340,7 +340,7 @@ ExportNotesSheet.present = (notes, allNotes) => {
const styles = StyleSheet.create({
container: {
- ...getElevation(5),
+ ...getElevationStyle(5),
borderRadius: 5,
paddingVertical: pv
},
diff --git a/apps/mobile/app/components/sheets/recovery-key/index.js b/apps/mobile/app/components/sheets/recovery-key/index.js
index e7562bdca..167ae2b29 100644
--- a/apps/mobile/app/components/sheets/recovery-key/index.js
+++ b/apps/mobile/app/components/sheets/recovery-key/index.js
@@ -34,7 +34,6 @@ import SettingsService from "../../../services/settings";
import { db } from "../../../common/database";
import Storage from "../../../common/database/storage";
import { eOpenRecoveryKeyDialog } from "../../../utils/events";
-import { sanitizeFilename } from "../../../utils/sanitizer";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import DialogHeader from "../../dialog/dialog-header";
@@ -44,6 +43,7 @@ import SheetWrapper from "../../ui/sheet";
import { QRCode } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import RNFetchBlob from "react-native-blob-util";
+import { sanitizeFilename } from "@notesnook/common";
class RecoveryKeySheet extends React.Component {
constructor(props) {
diff --git a/apps/mobile/app/components/sheets/restore-data/index.js b/apps/mobile/app/components/sheets/restore-data/index.js
index 664776edc..8e61f80b9 100644
--- a/apps/mobile/app/components/sheets/restore-data/index.js
+++ b/apps/mobile/app/components/sheets/restore-data/index.js
@@ -36,7 +36,6 @@ import { initialize } from "../../../stores";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
-import { getFormattedDate } from "../../../utils/time";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
@@ -45,6 +44,7 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
+import { getFormattedDate } from "@notesnook/common";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
diff --git a/apps/mobile/app/components/sheets/topic-sheet/index.tsx b/apps/mobile/app/components/sheets/topic-sheet/index.tsx
index 9d7ff9ccc..90dac8d1d 100644
--- a/apps/mobile/app/components/sheets/topic-sheet/index.tsx
+++ b/apps/mobile/app/components/sheets/topic-sheet/index.tsx
@@ -60,11 +60,12 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { MMKV } from "../../../common/database/mmkv";
import { openEditor } from "../../../screens/notes/common";
-import { getTotalNotes, history } from "../../../utils";
+import { getTotalNotes } from "@notesnook/common";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Properties } from "../../properties";
import Sort from "../sort";
+import { useSelectionStore } from "../../../stores/use-selection-store";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {
@@ -333,7 +334,9 @@ export const TopicsSheet = () => {
}}
onPress={async () => {
//@ts-ignore
- history.selectedItemsList = selection;
+ useSelectionStore.setState({
+ selectedItemsList: selection
+ });
presentDialog({
title: `Delete ${
selection.length > 1 ? "topics" : "topics"
@@ -345,7 +348,7 @@ export const TopicsSheet = () => {
negativeText: "Cancel",
positivePress: async () => {
await deleteItems();
- history.selectedItemsList = [];
+ useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
},
diff --git a/apps/mobile/app/components/toast/index.js b/apps/mobile/app/components/toast/index.js
index e48fe488a..17b004cbd 100644
--- a/apps/mobile/app/components/toast/index.js
+++ b/apps/mobile/app/components/toast/index.js
@@ -28,7 +28,7 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import { eHideToast, eShowToast } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
@@ -136,7 +136,7 @@ export const Toast = ({ context = "global" }) => {
>
{
const colors = useThemeStore((state) => state.colors);
@@ -67,7 +67,7 @@ export const IconButton = ({
return;
}
if (tooltipText) {
- showTooltip(event, tooltipText, tooltipPosition);
+ NativeTooltip.show(event, tooltipText, tooltipPosition);
}
};
diff --git a/apps/mobile/app/components/ui/input/index.tsx b/apps/mobile/app/components/ui/input/index.tsx
index 3f343c10e..25acedf81 100644
--- a/apps/mobile/app/components/ui/input/index.tsx
+++ b/apps/mobile/app/components/ui/input/index.tsx
@@ -36,7 +36,7 @@ import {
validateUsername
} from "../../../services/validation";
import { useThemeStore } from "../../../stores/use-theme-store";
-import { getElevation } from "../../../utils";
+import { getElevationStyle } from "../../../utils/elevation";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
@@ -344,7 +344,7 @@ const Input = ({
paddingVertical: 3,
paddingHorizontal: 5,
borderRadius: 2.5,
- ...getElevation(2),
+ ...getElevationStyle(2),
top: 0
}}
>
diff --git a/apps/mobile/app/components/ui/reminder-time/index.tsx b/apps/mobile/app/components/ui/reminder-time/index.tsx
index 32e08ea22..669ea34b0 100644
--- a/apps/mobile/app/components/ui/reminder-time/index.tsx
+++ b/apps/mobile/app/components/ui/reminder-time/index.tsx
@@ -23,8 +23,8 @@ import { ViewStyle } from "react-native";
import { Reminder } from "../../../services/notifications";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
-import { getFormattedReminderTime } from "../../../utils/time";
import { Button, ButtonProps } from "../button";
+import { getFormattedReminderTime } from "@notesnook/common";
export const ReminderTime = ({
checkIsActive = true,
diff --git a/apps/mobile/app/components/ui/time-since/index.tsx b/apps/mobile/app/components/ui/time-since/index.tsx
index e4c2f2677..fbd8c3857 100644
--- a/apps/mobile/app/components/ui/time-since/index.tsx
+++ b/apps/mobile/app/components/ui/time-since/index.tsx
@@ -17,9 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import React, { useEffect, useRef, useState } from "react";
+import { useTimeAgo } from "@notesnook/common";
+import React from "react";
import { TextProps } from "react-native";
-import { timeSince } from "../../../utils/time";
import Heading from "../typography/heading";
import Paragraph from "../typography/paragraph";
interface TimeSinceProps extends TextProps {
@@ -34,20 +34,10 @@ export const TimeSince = ({
updateFrequency = 30000,
bold
}: TimeSinceProps) => {
- const [timeAgo, setTimeAgo] = useState(null);
- const interval = useRef();
-
- useEffect(() => {
- let t = timeSince(time || Date.now());
- setTimeAgo(t);
- interval.current = setInterval(() => {
- t = timeSince(time);
- setTimeAgo(t);
- }, updateFrequency);
- return () => {
- interval.current && clearInterval(interval.current);
- };
- }, [time, updateFrequency]);
+ const timeAgo = useTimeAgo(time, {
+ interval: updateFrequency,
+ locale: "short"
+ });
return bold ? (
{timeAgo}
diff --git a/apps/mobile/app/components/walkthroughs/walkthroughs.tsx b/apps/mobile/app/components/walkthroughs/walkthroughs.tsx
index 93153d2e0..cd3a7021e 100644
--- a/apps/mobile/app/components/walkthroughs/walkthroughs.tsx
+++ b/apps/mobile/app/components/walkthroughs/walkthroughs.tsx
@@ -28,7 +28,7 @@ import {
} from "../../assets/images/assets";
import { ThemeStore, useThemeStore } from "../../stores/use-theme-store";
import { eSendEvent } from "../../services/event-manager";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import { eOpenAddNotebookDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import useRotator from "../../hooks/use-rotator";
@@ -89,7 +89,7 @@ const NotebookWelcome = () => {
padding: 12,
width: "100%",
backgroundColor: colors.bg,
- ...getElevation(3),
+ ...getElevationStyle(3),
borderRadius: 10,
marginVertical: 12
}}
@@ -142,7 +142,7 @@ const notebooks: { id: string; steps: TStep[] } = {
padding: 12,
width: "100%",
backgroundColor: colors.bg,
- ...getElevation(3),
+ ...getElevationStyle(3),
borderRadius: 10,
marginVertical: 12
}}
diff --git a/apps/mobile/app/hooks/use-actions.js b/apps/mobile/app/hooks/use-actions.js
index 6307cbfae..5532f488d 100644
--- a/apps/mobile/app/hooks/use-actions.js
+++ b/apps/mobile/app/hooks/use-actions.js
@@ -50,7 +50,7 @@ import { useSelectionStore } from "../stores/use-selection-store";
import { useTagStore } from "../stores/use-tag-store";
import { useThemeStore } from "../stores/use-theme-store";
import { useUserStore } from "../stores/use-user-store";
-import { toTXT } from "../utils";
+import { convertNoteToText } from "../utils/note-to-text";
import { toggleDarkMode } from "../utils/color-scheme/utils";
import {
eOnTopicSheetUpdate,
@@ -182,7 +182,7 @@ export const useActions = ({ close = () => null, item }) => {
});
return;
}
- let text = await toTXT(item, false);
+ let text = await convertNoteToText(item, false);
let html = text.replace(/\n/g, "
");
Notifications.displayNotification({
title: item.title,
@@ -227,7 +227,7 @@ export const useActions = ({ close = () => null, item }) => {
description: "Unlock note to copy to clipboard."
});
} else {
- Clipboard.setString(await toTXT(item));
+ Clipboard.setString(await convertNoteToText(item));
ToastEvent.show({
heading: "Note copied to clipboard",
type: "success",
@@ -415,7 +415,7 @@ export const useActions = ({ close = () => null, item }) => {
Share.open({
title: "Share note to",
failOnCancel: false,
- message: await toTXT(item)
+ message: await convertNoteToText(item)
});
}
}
diff --git a/apps/mobile/app/navigation/navigation-stack.js b/apps/mobile/app/navigation/navigation-stack.js
index e56b747cf..d0498bf29 100644
--- a/apps/mobile/app/navigation/navigation-stack.js
+++ b/apps/mobile/app/navigation/navigation-stack.js
@@ -48,7 +48,6 @@ import { useNoteStore } from "../stores/use-notes-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useThemeStore } from "../stores/use-theme-store";
-import { history } from "../utils";
import { rootNavigatorRef } from "../utils/global-refs";
import Auth from "../components/auth";
const NativeStack = createNativeStackNavigator();
@@ -191,7 +190,7 @@ const _NavigationStack = () => {
const clearSelection = useSelectionStore((state) => state.clearSelection);
const onStateChange = React.useCallback(() => {
- if (history.selectionMode) {
+ if (useSelectionStore.getState().selectionMode) {
clearSelection(true);
}
hideAllTooltips();
diff --git a/apps/mobile/app/navigation/tabs-holder.js b/apps/mobile/app/navigation/tabs-holder.js
index 894742ff7..eccdef403 100644
--- a/apps/mobile/app/navigation/tabs-holder.js
+++ b/apps/mobile/app/navigation/tabs-holder.js
@@ -59,7 +59,6 @@ import {
import { useEditorStore } from "../stores/use-editor-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useThemeStore } from "../stores/use-theme-store";
-import { setWidthHeight } from "../utils";
import {
eClearEditor,
eCloseFullscreenEditor,
@@ -221,7 +220,6 @@ const _TabsHolder = () => {
width: size.width,
height: size.height
});
- setWidthHeight(size);
DDS.setSize(size, orientation);
const nextDeviceMode = DDS.isLargeTablet()
? "tablet"
diff --git a/apps/mobile/app/screens/editor/index.tsx b/apps/mobile/app/screens/editor/index.tsx
index 53f42cab4..380c19cec 100755
--- a/apps/mobile/app/screens/editor/index.tsx
+++ b/apps/mobile/app/screens/editor/index.tsx
@@ -37,7 +37,7 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import { useEditorStore } from "../../stores/use-editor-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions";
import { NoteType } from "../../utils/types";
import { EDITOR_URI } from "./source";
@@ -256,7 +256,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
width: 60,
height: 60,
right: 12,
- ...getElevation(5)
+ ...getElevationStyle(5)
}}
/>
) : null;
diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor.ts b/apps/mobile/app/screens/editor/tiptap/use-editor.ts
index ee5413306..acc61de0f 100644
--- a/apps/mobile/app/screens/editor/tiptap/use-editor.ts
+++ b/apps/mobile/app/screens/editor/tiptap/use-editor.ts
@@ -38,7 +38,6 @@ 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 { getFormattedDate } from "../../../utils/time";
import { NoteType } from "../../../utils/types";
import { onNoteCreated } from "../../notes/common";
import Commands from "./commands";
@@ -53,6 +52,7 @@ import {
makeSessionId,
post
} from "./utils";
+import { getFormattedDate } from "@notesnook/common";
export const useEditor = (
editorId = "",
@@ -139,7 +139,7 @@ export const useEditor = (
const reset = useCallback(
async (resetState = true, resetContent = true) => {
- currentNote.current?.id && db.fs.cancel(currentNote.current.id);
+ currentNote.current?.id && db.fs?.cancel(currentNote.current.id);
currentNote.current = null;
loadedImages.current = {};
currentContent.current = null;
diff --git a/apps/mobile/app/screens/settings/2fa.tsx b/apps/mobile/app/screens/settings/2fa.tsx
index 57d32d582..9050b2f6b 100644
--- a/apps/mobile/app/screens/settings/2fa.tsx
+++ b/apps/mobile/app/screens/settings/2fa.tsx
@@ -49,9 +49,9 @@ import {
import { ThemeStore, useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet } from "../../utils/events";
-import { sanitizeFilename } from "../../utils/sanitizer";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
+import { sanitizeFilename } from "@notesnook/common";
const mfaMethods: MFAMethod[] = [
{
id: "app",
diff --git a/apps/mobile/app/screens/settings/app-lock.js b/apps/mobile/app/screens/settings/app-lock.js
index f77e508db..afbf4c706 100644
--- a/apps/mobile/app/screens/settings/app-lock.js
+++ b/apps/mobile/app/screens/settings/app-lock.js
@@ -39,7 +39,7 @@ import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
-import { getElevation } from "../../utils";
+import { getElevationStyle } from "../../utils/elevation";
import { eOpenLoginDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
const AppLock = ({ route }) => {
@@ -274,7 +274,7 @@ const AppLock = ({ route }) => {
style={{
paddingHorizontal: 24,
alignSelf: "center",
- ...getElevation(5),
+ ...getElevationStyle(5),
marginTop: 30,
borderRadius: 100
}}
diff --git a/apps/mobile/app/screens/settings/appearance.js b/apps/mobile/app/screens/settings/appearance.js
index dd2762e76..8213f71ba 100644
--- a/apps/mobile/app/screens/settings/appearance.js
+++ b/apps/mobile/app/screens/settings/appearance.js
@@ -17,107 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import React, { useRef, useState } from "react";
+import React 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 { PressableButton } from "../../components/ui/pressable";
-import Paragraph from "../../components/ui/typography/paragraph";
import { DDS } from "../../services/device-detection";
-import { ToastEvent } from "../../services/event-manager";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
-import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import {
- hexToRGBA,
RGB_Linear_Shade,
+ hexToRGBA,
switchAccentColor
} from "../../utils/color-scheme/utils";
-import { MenuItemsList } from "../../utils/constants";
import { SIZE } from "../../utils/size";
-export const HomagePageSelector = () => {
- const colors = useThemeStore((state) => state.colors);
- const settings = useSettingStore((state) => state.settings);
- const menuRef = useRef();
- const [width, setWidth] = useState(0);
- return (
- {
- setWidth(event.nativeEvent.layout.width);
- }}
- style={{
- width: "100%"
- }}
- >
-
-
- );
-};
export const AccentColorPicker = () => {
const colors = useThemeStore((state) => state.colors);
diff --git a/apps/mobile/app/screens/settings/backup-restore.js b/apps/mobile/app/screens/settings/backup-restore.js
deleted file mode 100644
index fb8d01ee9..000000000
--- a/apps/mobile/app/screens/settings/backup-restore.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
-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 React from "react";
-import { Platform, TouchableOpacity, View } from "react-native";
-import Paragraph from "../../components/ui/typography/paragraph";
-import Backup from "../../services/backup";
-import PremiumService from "../../services/premium";
-import SettingsService from "../../services/settings";
-import { useSettingStore } from "../../stores/use-setting-store";
-import { useThemeStore } from "../../stores/use-theme-store";
-import { SIZE } from "../../utils/size";
-export const AutomaticBackupsSelector = () => {
- const colors = useThemeStore((state) => state.colors);
- const settings = useSettingStore((state) => state.settings);
- const updateAskForBackup = async () => {
- SettingsService.set({
- nextBackupRequestTime: Date.now() + 86400000 * 3
- });
- };
-
- return (
-
- {[
- {
- title: "Never",
- value: "useroff"
- },
- {
- title: "Daily",
- value: "daily"
- },
- {
- title: "Weekly",
- value: "weekly"
- },
- {
- title: "Monthly",
- value: "monthly"
- }
- ].map((item, index) => (
- {
- if (item.value === "useroff") {
- await SettingsService.set({ reminder: item.value });
- } else {
- await PremiumService.verify(async () => {
- if (Platform.OS === "android") {
- let granted = await Backup.checkBackupDirExists();
- if (!granted) {
- return;
- }
- }
- await SettingsService.set({ reminder: item.value });
- });
- }
- updateAskForBackup();
- }}
- key={item.value}
- style={{
- backgroundColor:
- settings.reminder === item.value ? colors.accent : colors.nav,
- justifyContent: "center",
- alignItems: "center",
- width: "25%",
- height: 35,
- borderRightWidth: index !== 3 ? 1 : 0,
- borderRightColor: colors.border
- }}
- >
-
- {item.title}
-
-
- ))}
-
- );
-};
diff --git a/apps/mobile/app/screens/settings/components.tsx b/apps/mobile/app/screens/settings/components.tsx
index ee88a2e17..351b8c045 100644
--- a/apps/mobile/app/screens/settings/components.tsx
+++ b/apps/mobile/app/screens/settings/components.tsx
@@ -18,29 +18,34 @@ along with this program. If not, see .
*/
import React, { ReactElement } from "react";
-import { AccentColorPicker, HomagePageSelector } from "./appearance";
-import { AutomaticBackupsSelector } from "./backup-restore";
+import { AccentColorPicker } from "./appearance";
import DebugLogs from "./debug";
import { ConfigureToolbar } from "./editor/configure-toolbar";
import { Licenses } from "./licenses";
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";
+import {
+ HomePicker,
+ DateFormatPicker,
+ FontPicker,
+ TimeFormatPicker,
+ TrashIntervalPicker,
+ BackupReminderPicker
+} from "./picker/pickers";
+
export const components: { [name: string]: ReactElement } = {
colorpicker: ,
- homeselector: ,
- autobackups: ,
+ homeselector: ,
+ autobackups: ,
subscription: ,
configuretoolbar: ,
"debug-logs": ,
"sound-picker": ,
licenses: ,
- "trash-interval-selector": ,
- "font-selector": ,
+ "trash-interval-selector": ,
+ "font-selector": ,
"title-format": ,
- "date-format-selector": ,
- "time-format-selector":
+ "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
deleted file mode 100644
index 51ba044df..000000000
--- a/apps/mobile/app/screens/settings/date-format.jsx
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
-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";
-import { useSettingStore } from "../../stores/use-setting-store";
-
-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);
- useSettingStore.setState({
- dateFormat: item
- });
- };
-
- return (
- {
- setWidth(event.nativeEvent.layout.width);
- }}
- style={{
- width: "100%"
- }}
- >
-
-
- );
-};
-
-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);
- useSettingStore.setState({
- timeFormat: item
- });
- };
-
- const TimeFormats = {
- "12-hour": "hh:mm A",
- "24-hour": "HH:mm"
- };
-
- return (
- {
- setWidth(event.nativeEvent.layout.width);
- }}
- style={{
- width: "100%"
- }}
- >
-
-
- );
-};
diff --git a/apps/mobile/app/screens/settings/debug.tsx b/apps/mobile/app/screens/settings/debug.tsx
index 50b0ccfbe..4a46f91af 100644
--- a/apps/mobile/app/screens/settings/debug.tsx
+++ b/apps/mobile/app/screens/settings/debug.tsx
@@ -33,7 +33,8 @@ import useTimer from "../../hooks/use-timer";
import { ToastEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { hexToRGBA } from "../../utils/color-scheme/utils";
-import { sanitizeFilename } from "../../utils/sanitizer";
+import { sanitizeFilename } from "@notesnook/common";
+
// function getLevelString(level: number) {
// switch (level) {
// case LogLevel.Debug:
diff --git a/apps/mobile/app/screens/settings/editor/group.tsx b/apps/mobile/app/screens/settings/editor/group.tsx
index 50b0b55a0..c1d76ed10 100644
--- a/apps/mobile/app/screens/settings/editor/group.tsx
+++ b/apps/mobile/app/screens/settings/editor/group.tsx
@@ -25,7 +25,7 @@ import { presentDialog } from "../../../components/dialog/functions";
import { IconButton } from "../../../components/ui/icon-button";
import Paragraph from "../../../components/ui/typography/paragraph";
import { useThemeStore } from "../../../stores/use-theme-store";
-import { getElevation } from "../../../utils";
+import { getElevationStyle } from "../../../utils/elevation";
import { SIZE } from "../../../utils/size";
import { renderTool } from "./common";
import { DraggableItem, useDragState } from "./state";
@@ -174,7 +174,7 @@ export const Group = ({
width: isDragged ? dimensions.current?.width : "100%",
backgroundColor: colors.bg,
borderRadius: 10,
- ...getElevation(hover ? 5 : 0),
+ ...getElevationStyle(hover ? 5 : 0),
marginTop: isSubgroup ? 0 : 10
}
]}
diff --git a/apps/mobile/app/screens/settings/editor/tool.tsx b/apps/mobile/app/screens/settings/editor/tool.tsx
index 19788da38..95022d9dd 100644
--- a/apps/mobile/app/screens/settings/editor/tool.tsx
+++ b/apps/mobile/app/screens/settings/editor/tool.tsx
@@ -26,7 +26,7 @@ import { IconButton } from "../../../components/ui/icon-button";
import { SvgView } from "../../../components/ui/svg";
import Paragraph from "../../../components/ui/typography/paragraph";
import { useThemeStore } from "../../../stores/use-theme-store";
-import { getElevation } from "../../../utils";
+import { getElevationStyle } from "../../../utils/elevation";
import { SIZE } from "../../../utils/size";
import { renderGroup } from "./common";
import { DraggableItem, useDragState } from "./state";
@@ -186,7 +186,7 @@ export const Tool = ({
alignItems: "center",
justifyContent: "space-between",
paddingLeft: isSubgroup ? 30 : 12,
- ...getElevation(hover ? 3 : 0)
+ ...getElevationStyle(hover ? 3 : 0)
}}
>
.
-*/
-
-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 { PressableButton } from "../../components/ui/pressable";
-import Paragraph from "../../components/ui/typography/paragraph";
-import SettingsService from "../../services/settings";
-import { useSettingStore } from "../../stores/use-setting-store";
-import { useThemeStore } from "../../stores/use-theme-store";
-import { SIZE } from "../../utils/size";
-import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
-
-export const FontSelector = () => {
- const colors = useThemeStore((state) => state.colors);
- const defaultFontFamily = useSettingStore(
- (state) => state.settings.defaultFontFamily
- );
- const menuRef = useRef();
- const [width, setWidth] = useState(0);
-
- const onChange = (item) => {
- menuRef.current?.hide();
- SettingsService.set({
- defaultFontFamily: item
- });
- };
- return (
- {
- setWidth(event.nativeEvent.layout.width);
- }}
- style={{
- width: "100%"
- }}
- >
-
-
- );
-};
diff --git a/apps/mobile/app/screens/settings/trash-interval-selector.jsx b/apps/mobile/app/screens/settings/picker/index.tsx
similarity index 57%
rename from apps/mobile/app/screens/settings/trash-interval-selector.jsx
rename to apps/mobile/app/screens/settings/picker/index.tsx
index 6ab611707..0dacb1c58 100644
--- a/apps/mobile/app/screens/settings/trash-interval-selector.jsx
+++ b/apps/mobile/app/screens/settings/picker/index.tsx
@@ -21,25 +21,51 @@ 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 PremiumService from "../../services/premium";
-import { useThemeStore } from "../../stores/use-theme-store";
-import { SIZE } from "../../utils/size";
+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 PremiumService from "../../../services/premium";
-export const TrashIntervalSelector = () => {
+interface PickerOptions {
+ getValue: () => T;
+ updateValue: (item: T) => Promise;
+ formatValue: (item: T) => any;
+ compareValue: (current: T, item: T) => boolean;
+ getItemKey: (item: T) => string;
+ options: T[];
+ premium?: boolean;
+ onCheckOptionIsPremium?: (item: T) => boolean;
+}
+
+export function SettingsPicker({
+ getValue,
+ updateValue,
+ formatValue,
+ compareValue,
+ options,
+ getItemKey,
+ premium,
+ onCheckOptionIsPremium = () => true
+}: PickerOptions) {
const colors = useThemeStore((state) => state.colors);
- const [trashInterval, setTrashInterval] = useState(
- db.settings.getTrashCleanupInterval()
- );
- const menuRef = useRef();
+ const menuRef = useRef();
const [width, setWidth] = useState(0);
+ const [currentValue, setCurrentValue] = useState(getValue());
+
+ const onChange = async (item: T) => {
+ if (premium && onCheckOptionIsPremium?.(item)) {
+ await PremiumService.verify(async () => {
+ menuRef.current?.hide();
+ await updateValue(item);
+ setCurrentValue(item);
+ });
+ return;
+ }
- const onChange = (item) => {
menuRef.current?.hide();
- setTrashInterval(item);
- db.settings.setTrashCleanupInterval(item);
+ await updateValue(item);
+ setCurrentValue(item);
};
return (
@@ -78,40 +104,42 @@ export const TrashIntervalSelector = () => {
padding: 12
}}
>
-
- {trashInterval === -1 ? "Never" : trashInterval + " days"}
-
+ {formatValue(currentValue)}
}
>
- {[-1, 7, 30, 365].map((item) => (
+ {options.map((item) => (
))}
);
-};
+}
+
+export function createSettingsPicker(props: PickerOptions) {
+ const Selector = () => {
+ return ;
+ };
+ return Selector;
+}
diff --git a/apps/mobile/app/screens/settings/picker/pickers.jsx b/apps/mobile/app/screens/settings/picker/pickers.jsx
new file mode 100644
index 000000000..60584dca5
--- /dev/null
+++ b/apps/mobile/app/screens/settings/picker/pickers.jsx
@@ -0,0 +1,130 @@
+/*
+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 { db } from "../../../common/database";
+import { ToastEvent } from "../../../services/event-manager";
+import SettingsService from "../../../services/settings";
+import { useSettingStore } from "../../../stores/use-setting-store";
+import { MenuItemsList } from "../../../utils/constants";
+import { createSettingsPicker } from ".";
+import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
+import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common";
+import dayjs from "dayjs";
+
+export const FontPicker = createSettingsPicker({
+ getValue: () => useSettingStore.getState().settings.defaultFontFamily,
+ updateValue: (item) => {
+ SettingsService.set({
+ defaultFontFamily: item
+ });
+ },
+ formatValue: (item) => {
+ return getFontById(typeof item === "object" ? item.id : item).title;
+ },
+ getItemKey: (item) => item.id,
+ options: getFonts(),
+ compareValue: (current, item) => current === item.id
+});
+
+export const HomePicker = createSettingsPicker({
+ getValue: () => useSettingStore.getState().settings.homepage,
+ updateValue: (item) => {
+ SettingsService.set({ homepage: item.name });
+ ToastEvent.show({
+ heading: "Homepage set to " + item.name,
+ message: "Restart the app for changes to take effect.",
+ type: "success"
+ });
+ },
+ formatValue: (item) => {
+ return typeof item === "object" ? item.name : item;
+ },
+ getItemKey: (item) => item.name,
+ options: MenuItemsList.slice(0, MenuItemsList.length - 1),
+ compareValue: (current, item) => current === item.name,
+ premium: true
+});
+
+export const TrashIntervalPicker = createSettingsPicker({
+ getValue: () => db.settings.getTrashCleanupInterval(),
+ updateValue: (item) => {
+ db.settings.setTrashCleanupInterval(item);
+ },
+ formatValue: (item) => {
+ return item === -1 ? "Never" : item + " days";
+ },
+ getItemKey: (item) => item.toString(),
+ options: [-1, 7, 30, 365],
+ compareValue: (current, item) => current === item,
+ premium: true
+});
+
+export const DateFormatPicker = createSettingsPicker({
+ getValue: () => db.settings.getDateFormat(),
+ updateValue: (item) => {
+ db.settings.setDateFormat(item);
+ useSettingStore.setState({
+ dateFormat: item
+ });
+ },
+ formatValue: (item) => {
+ return `${item} (${dayjs().format(item)})`;
+ },
+ getItemKey: (item) => item,
+ options: DATE_FORMATS,
+ compareValue: (current, item) => current === item
+});
+
+const TimeFormats = {
+ "12-hour": "hh:mm A",
+ "24-hour": "HH:mm"
+};
+
+export const TimeFormatPicker = createSettingsPicker({
+ getValue: () => db.settings.getTimeFormat(),
+ updateValue: (item) => {
+ db.settings.setTimeFormat(item);
+ useSettingStore.setState({
+ timeFormat: item
+ });
+ },
+ formatValue: (item) => {
+ return `${item} (${dayjs().format(TimeFormats[item])})`;
+ },
+ getItemKey: (item) => item,
+ options: TIME_FORMATS,
+ compareValue: (current, item) => current === item
+});
+
+export const BackupReminderPicker = createSettingsPicker({
+ getValue: () => useSettingStore.getState().settings.reminder,
+ updateValue: (item) => {
+ SettingsService.set({ reminder: item });
+ },
+ formatValue: (item) => {
+ return item === "useroff"
+ ? "Never"
+ : item.slice(0, 1).toUpperCase() + item.slice(1);
+ },
+ getItemKey: (item) => item,
+ options: ["useroff", "daily", "weekly", "monthly"],
+ compareValue: (current, item) => current === item,
+ premium: true,
+ onCheckOptionIsPremium: (item) => item !== "useroff"
+});
diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx
index f79a302f9..1ed78f19b 100644
--- a/apps/mobile/app/screens/settings/settings-data.tsx
+++ b/apps/mobile/app/screens/settings/settings-data.tsx
@@ -35,14 +35,14 @@ import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
import { Progress } from "../../components/sheets/progress";
import { Update } from "../../components/sheets/update";
-import { useVaultStatus, VaultStatusType } from "../../hooks/use-vault-status";
+import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
import BackupService from "../../services/backup";
import BiometicService from "../../services/biometrics";
import {
+ ToastEvent,
eSendEvent,
openVault,
- presentSheet,
- ToastEvent
+ presentSheet
} from "../../services/event-manager";
import { setLoginMessage } from "../../services/message";
import Navigation from "../../services/navigation";
@@ -53,7 +53,6 @@ import Sync from "../../services/sync";
import { clearAllStores } from "../../stores";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
-import { AndroidModule } from "../../utils";
import { getColorScheme, toggleDarkMode } from "../../utils/color-scheme/utils";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import {
@@ -63,6 +62,7 @@ import {
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
} from "../../utils/events";
+import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time";
import { MFARecoveryCodes, MFASheet } from "./2fa";
import AppLock from "./app-lock";
@@ -762,7 +762,7 @@ export const settingsGroups: SettingSection[] = [
modifer: () => {
const settings = SettingsService.get();
Platform.OS === "android"
- ? AndroidModule.setSecureMode(!settings.privacyScreen)
+ ? NotesnookModule.setSecureMode(!settings.privacyScreen)
: enabled(true);
SettingsService.set({ privacyScreen: !settings.privacyScreen });
diff --git a/apps/mobile/app/services/background-sync.ts b/apps/mobile/app/services/background-sync.ts
index db65c0e28..07d06df28 100644
--- a/apps/mobile/app/services/background-sync.ts
+++ b/apps/mobile/app/services/background-sync.ts
@@ -16,6 +16,34 @@ 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 { Platform } from "react-native";
+import {
+ beginBackgroundTask,
+ endBackgroundTask
+} from "react-native-begin-background-task";
+
+async function doInBackground(callback: () => Promise) {
+ if (Platform.OS === "ios") {
+ try {
+ const bgTaskId = await beginBackgroundTask();
+ const result = await callback();
+ await endBackgroundTask(bgTaskId);
+ return result;
+ } catch (e) {
+ return (e as Error).message;
+ }
+ } else {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async (resolve) => {
+ try {
+ const result = await callback();
+ resolve(result);
+ } catch (e) {
+ resolve((e as Error).message);
+ }
+ });
+ }
+}
// import BackgroundFetch from "react-native-background-fetch";
// import { DatabaseLogger, db } from "../common/database";
@@ -120,4 +148,4 @@ along with this program. If not, see .
// start,
// registerHeadlessTask
// };
-export {};
+export default { doInBackground };
diff --git a/apps/mobile/app/services/backup.js b/apps/mobile/app/services/backup.js
index d70a2b3a6..9238ec736 100644
--- a/apps/mobile/app/services/backup.js
+++ b/apps/mobile/app/services/backup.js
@@ -26,11 +26,11 @@ import { presentDialog } from "../components/dialog/functions";
import { DatabaseLogger, db } from "../common/database";
import storage from "../common/database/storage";
import { eCloseSheet } from "../utils/events";
-import { sanitizeFilename } from "../utils/sanitizer";
import { sleep } from "../utils/time";
import { eSendEvent, presentSheet, ToastEvent } from "./event-manager";
import SettingsService from "./settings";
import PremiumService from "./premium";
+import { sanitizeFilename } from "@notesnook/common";
const MS_DAY = 86400000;
const MS_WEEK = MS_DAY * 7;
diff --git a/apps/mobile/app/services/exporter.js b/apps/mobile/app/services/exporter.js
index c89df340d..4b4644a62 100644
--- a/apps/mobile/app/services/exporter.js
+++ b/apps/mobile/app/services/exporter.js
@@ -25,9 +25,10 @@ import * as ScopedStorage from "react-native-scoped-storage";
import RNFetchBlob from "react-native-blob-util";
import { DatabaseLogger, db } from "../common/database/index";
import Storage from "../common/database/storage";
-import { toTXT } from "../utils";
-import { sanitizeFilename } from "../utils/sanitizer";
+import { convertNoteToText } from "../utils/note-to-text";
+
import { sleep } from "../utils/time";
+import { sanitizeFilename } from "@notesnook/common";
const MIMETypes = {
txt: "text/plain",
@@ -149,7 +150,7 @@ async function exportAs(type, note, bulk) {
}
break;
case "txt":
- data = await toTXT(note);
+ data = await convertNoteToText(note);
break;
}
diff --git a/apps/mobile/app/services/settings.ts b/apps/mobile/app/services/settings.ts
index 41f9d31ec..7e1343bfb 100644
--- a/apps/mobile/app/services/settings.ts
+++ b/apps/mobile/app/services/settings.ts
@@ -22,8 +22,8 @@ import Orientation from "react-native-orientation";
import { enabled } from "react-native-privacy-snapshot";
import { MMKV } from "../common/database/mmkv";
import { SettingStore, useSettingStore } from "../stores/use-setting-store";
-import { AndroidModule } from "../utils";
import { getColorScheme } from "../utils/color-scheme/utils";
+import { NotesnookModule } from "../utils/notesnook-module";
import { scale, updateSize } from "../utils/size";
import { DDS } from "./device-detection";
import { setAutobackOffMessage } from "./message";
@@ -63,13 +63,13 @@ function init() {
function setPrivacyScreen(settings: SettingStore["settings"]) {
if (settings.privacyScreen || settings.appLockMode === "background") {
if (Platform.OS === "android") {
- AndroidModule.setSecureMode(true);
+ NotesnookModule.setSecureMode(true);
} else {
enabled(true);
}
} else {
if (Platform.OS === "android") {
- AndroidModule.setSecureMode(false);
+ NotesnookModule.setSecureMode(false);
} else {
enabled(false);
}
diff --git a/apps/mobile/app/services/sync.js b/apps/mobile/app/services/sync.js
index 6ef5642f4..21243a6a1 100644
--- a/apps/mobile/app/services/sync.js
+++ b/apps/mobile/app/services/sync.js
@@ -17,15 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import NetInfo from "@react-native-community/netinfo";
import { EVENTS } from "@notesnook/core/common";
-import { initAfterSync } from "../stores/index";
-import { SyncStatus, useUserStore } from "../stores/use-user-store";
-import { doInBackground } from "../utils";
+import NetInfo from "@react-native-community/netinfo";
import { db } from "../common/database";
import { DatabaseLogger } from "../common/database/index";
+import { initAfterSync } from "../stores/index";
+import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { ToastEvent } from "./event-manager";
import SettingsService from "./settings";
+import BackgroundSync from "./background-sync";
export const ignoredMessages = [
"Sync already running",
@@ -61,7 +61,7 @@ const run = async (
let error = null;
try {
- let res = await doInBackground(async () => {
+ let res = await BackgroundSync.doInBackground(async () => {
try {
await db.sync(full, forced);
return true;
diff --git a/apps/mobile/app/stores/use-selection-store.ts b/apps/mobile/app/stores/use-selection-store.ts
index 32661402a..e14cae42a 100644
--- a/apps/mobile/app/stores/use-selection-store.ts
+++ b/apps/mobile/app/stores/use-selection-store.ts
@@ -18,7 +18,6 @@ along with this program. If not, see .
*/
import create, { State } from "zustand";
-import { history } from "../utils";
type Item = {
id: string;
@@ -30,23 +29,16 @@ export interface SelectionStore extends State {
setAll: (all: Array) => void;
setSelectionMode: (mode: boolean) => void;
setSelectedItem: (item: Item) => void;
- clearSelection: (noanimation: boolean) => void;
+ clearSelection: () => void;
}
export const useSelectionStore = create((set, get) => ({
selectedItemsList: [],
selectionMode: false,
setAll: (all) => {
- history.selectedItemsList = all as never[];
set({ selectedItemsList: all });
},
setSelectionMode: (mode) => {
- if (!mode) {
- history.selectedItemsList = [];
- history.selectionMode = false;
- } else {
- history.selectionMode = true;
- }
set({
selectionMode: mode,
selectedItemsList: mode ? get().selectedItemsList : []
@@ -61,17 +53,13 @@ export const useSelectionStore = create((set, get) => ({
selectedItems.push(item);
}
selectedItems = [...new Set(selectedItems)];
- history.selectedItemsList = selectedItems as never[];
- history.selectionMode =
- selectedItems.length > 0 ? get().selectionMode : false;
+
set({
selectedItemsList: selectedItems,
- selectionMode: history.selectionMode
+ selectionMode: get().selectionMode
});
},
clearSelection: () => {
- history.selectedItemsList = [];
- history.selectionMode = false;
set({ selectionMode: false, selectedItemsList: [] });
}
}));
diff --git a/apps/mobile/app/utils/color-scheme/index.js b/apps/mobile/app/utils/color-scheme/index.js
index e6818f391..85bdd26e4 100644
--- a/apps/mobile/app/utils/color-scheme/index.js
+++ b/apps/mobile/app/utils/color-scheme/index.js
@@ -18,10 +18,10 @@ along with this program. If not, see .
*/
import { Platform, StatusBar } from "react-native";
-import { AndroidModule } from "..";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { eThemeUpdated } from "../events";
+import { NotesnookModule } from "../notesnook-module";
export const ACCENT = {
color: "#008837",
@@ -137,7 +137,7 @@ export function setColorScheme(colors = COLOR_SCHEME, accent = ACCENT) {
false
);
if (Platform.OS === "android") {
- AndroidModule.setBackgroundColor(COLOR_SCHEME.bg);
+ NotesnookModule.setBackgroundColor(COLOR_SCHEME.bg);
StatusBar.setBackgroundColor("transparent", false);
StatusBar.setTranslucent(true, false);
}
diff --git a/apps/mobile/app/utils/constants.js b/apps/mobile/app/utils/constants.js
index 03683d95e..c2c54e43c 100644
--- a/apps/mobile/app/utils/constants.js
+++ b/apps/mobile/app/utils/constants.js
@@ -100,16 +100,6 @@ export const SUBSCRIPTION_PROVIDER = {
}
};
-export const WeekDayNames = {
- 0: "Sunday",
- 1: "Monday",
- 2: "Tuesday",
- 3: "Wednesday",
- 4: "Thursday",
- 5: "Friday",
- 6: "Saturday"
-};
-
export const MenuItemsList = [
{
name: "Notes",
@@ -214,15 +204,3 @@ export const BUTTON_TYPES = {
opacity: 0.12
}
};
-
-export const bgTaskOptions = {
- taskName: "notesnookSync",
- taskTitle: "Notesnook Sync",
- taskDesc: "Syncing your notes.",
- taskIcon: {
- name: "ic_stat_name",
- type: "drawable"
- },
- color: "#ffffff",
- linkingURI: "com.streetwriters.notesnook://launch"
-};
diff --git a/apps/mobile/app/utils/elevation.ts b/apps/mobile/app/utils/elevation.ts
new file mode 100644
index 000000000..d477bdcbc
--- /dev/null
+++ b/apps/mobile/app/utils/elevation.ts
@@ -0,0 +1,28 @@
+/*
+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 .
+*/
+
+export function getElevationStyle(elevation: number) {
+ return {
+ elevation,
+ shadowColor: "black",
+ shadowOffset: { width: 0.3 * elevation, height: 0.5 * elevation },
+ shadowOpacity: 0.2,
+ shadowRadius: 0.7 * elevation
+ };
+}
diff --git a/apps/mobile/app/utils/functions.js b/apps/mobile/app/utils/functions.js
index 0fb597091..32a2f0e34 100644
--- a/apps/mobile/app/utils/functions.js
+++ b/apps/mobile/app/utils/functions.js
@@ -18,18 +18,17 @@ along with this program. If not, see .
*/
import { Linking } from "react-native";
-import { history } from ".";
+import { db } from "../common/database";
+import { presentDialog } from "../components/dialog/functions";
import { eSendEvent, ToastEvent } from "../services/event-manager";
import Navigation from "../services/navigation";
import SearchService from "../services/search";
-import { useSelectionStore } from "../stores/use-selection-store";
import { useMenuStore } from "../stores/use-menu-store";
-import { db } from "../common/database";
-import { eClearEditor, eOnTopicSheetUpdate } from "./events";
import { useRelationStore } from "../stores/use-relation-store";
-import { presentDialog } from "../components/dialog/functions";
+import { useSelectionStore } from "../stores/use-selection-store";
+import { eClearEditor, eOnTopicSheetUpdate } from "./events";
-function deleteConfirmDialog(items, type, context) {
+function confirmDeleteAllNotes(items, type, context) {
return new Promise((resolve) => {
presentDialog({
title: `Delete ${
@@ -68,33 +67,25 @@ export const deleteItems = async (item, context) => {
});
return;
}
- if (item && item.id && history.selectedItemsList.length === 0) {
- history.selectedItemsList = [];
- history.selectedItemsList.push(item);
+ if (
+ item &&
+ item.id &&
+ useSelectionStore.getState().selectedItemsList.length === 0
+ ) {
+ useSelectionStore.getState().setSelectedItem(item);
}
- let notes = history.selectedItemsList.filter((i) => i.type === "note");
- let notebooks = history.selectedItemsList.filter(
- (i) => i.type === "notebook"
- );
- let topics = history.selectedItemsList.filter((i) => i.type === "topic");
- let reminders = history.selectedItemsList.filter(
- (i) => i.type === "reminder"
- );
+ const { selectedItemsList } = useSelectionStore.getState();
- let routesForUpdate = [
- "TaggedNotes",
- "ColoredNotes",
- "TopicNotes",
- "Favorites",
- "Notes"
- ];
+ let notes = selectedItemsList.filter((i) => i.type === "note");
+ let notebooks = selectedItemsList.filter((i) => i.type === "notebook");
+ let topics = selectedItemsList.filter((i) => i.type === "topic");
+ let reminders = selectedItemsList.filter((i) => i.type === "reminder");
if (reminders.length > 0) {
for (let reminder of reminders) {
await db.reminders.remove(reminder.id);
}
- routesForUpdate.push("Reminders");
useRelationStore.getState().update();
}
@@ -111,12 +102,11 @@ export const deleteItems = async (item, context) => {
}
await db.notes.delete(note.id);
}
- routesForUpdate.push("Trash");
eSendEvent(eClearEditor);
}
if (topics?.length > 0) {
- const result = await deleteConfirmDialog(topics, "topic", context);
+ const result = await confirmDeleteAllNotes(topics, "topic", context);
if (result.delete) {
for (const topic of topics) {
if (result.deleteNotes) {
@@ -127,7 +117,6 @@ export const deleteItems = async (item, context) => {
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
- routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
@@ -137,7 +126,7 @@ export const deleteItems = async (item, context) => {
}
if (notebooks?.length > 0) {
- const result = await deleteConfirmDialog(notebooks, "notebook", context);
+ const result = await confirmDeleteAllNotes(notebooks, "notebook", context);
if (result.delete) {
let ids = notebooks.map((i) => i.id);
@@ -156,17 +145,17 @@ export const deleteItems = async (item, context) => {
}
}
await db.notebooks.delete(...ids);
- routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
}
}
Navigation.queueRoutesForUpdate();
- let msgPart = history.selectedItemsList.length === 1 ? " item" : " items";
- let message = history.selectedItemsList.length + msgPart + " moved to trash.";
+ let message = `${selectedItemsList.length} ${
+ selectedItemsList.length === 1 ? "item" : "items"
+ } moved to trash.`;
- let itemsCopy = [...history.selectedItemsList];
+ let deletedItems = [...selectedItemsList];
if (
topics.length === 0 &&
reminders.length === 0 &&
@@ -178,13 +167,12 @@ export const deleteItems = async (item, context) => {
func: async () => {
let trash = db.trash.all;
let ids = [];
- for (var i = 0; i < itemsCopy.length; i++) {
- let it = itemsCopy[i];
+ for (var i = 0; i < deletedItems.length; i++) {
+ let it = deletedItems[i];
let trashItem = trash.find((item) => item.id === it.id);
ids.push(trashItem.id);
}
await db.trash.restore(...ids);
-
Navigation.queueRoutesForUpdate();
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
@@ -193,9 +181,8 @@ export const deleteItems = async (item, context) => {
actionText: "Undo"
});
}
- history.selectedItemsList = [];
Navigation.queueRoutesForUpdate();
- useSelectionStore.getState().clearSelection(true);
+ useSelectionStore.getState().clearSelection();
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
SearchService.updateAndSearch();
@@ -204,8 +191,7 @@ export const deleteItems = async (item, context) => {
export const openLinkInBrowser = async (link) => {
try {
- const url = link;
- Linking.openURL(url);
+ Linking.openURL(link);
} catch (error) {
console.log(error.message);
}
diff --git a/apps/mobile/app/utils/index.js b/apps/mobile/app/utils/index.js
deleted file mode 100755
index 517c397e7..000000000
--- a/apps/mobile/app/utils/index.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
-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 { Dimensions, NativeModules, Platform } from "react-native";
-import {
- beginBackgroundTask,
- endBackgroundTask
-} from "react-native-begin-background-task";
-import RNTooltips from "react-native-tooltips";
-import { db } from "../common/database";
-import { tabBarRef } from "./global-refs";
-
-let prevTarget = null;
-
-export const TOOLTIP_POSITIONS = {
- LEFT: 1,
- RIGHT: 2,
- TOP: 3,
- BOTTOM: 4
-};
-
-export const sortSettings = {
- sort: "default",
- /**
- * @type {"desc" | "asc"}
- */
- sortOrder: "desc"
-};
-
-export const editing = {
- currentlyEditing: false,
- isFullscreen: false,
- actionAfterFirstSave: {
- type: null
- },
- isFocused: false,
- focusType: null,
- movedAway: true,
- tooltip: false,
- isRestoringState: false
-};
-export const selection = {
- data: [],
- type: null,
- selectedItems: []
-};
-
-export const history = {
- selectedItemsList: [],
- selectionMode: false
-};
-
-export function formatBytes(bytes, decimals = 2) {
- if (bytes === 0) return "0 Bytes";
-
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
-
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
-}
-
-export const AndroidModule = NativeModules.NNativeModule;
-
-export let dWidth = Dimensions.get("window").width;
-export let dHeight = Dimensions.get("window").height;
-
-export const InteractionManager = {
- runAfterInteractions: (func, time = 300) => setTimeout(func, time)
-};
-
-export function getElevation(elevation) {
- return {
- elevation,
- shadowColor: "black",
- shadowOffset: { width: 0.3 * elevation, height: 0.5 * elevation },
- shadowOpacity: 0.2,
- shadowRadius: 0.7 * elevation
- };
-}
-
-export async function doInBackground(cb) {
- if (Platform.OS === "ios") {
- let bgTaskId;
- try {
- bgTaskId = await beginBackgroundTask();
- let res = await cb();
- await endBackgroundTask(bgTaskId);
- return res;
- } catch (e) {
- return e.message;
- }
- } else {
- // eslint-disable-next-line no-async-promise-executor
- return new Promise(async (res) => {
- try {
- let result = await cb();
- res(result);
- } catch (e) {
- res(e.message);
- }
- });
- }
-}
-
-export function nth(n) {
- return (
- ["st", "nd", "rd"][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] ||
- "th"
- );
-}
-
-export function setWidthHeight(size) {
- dWidth = size.width;
- dHeight = size.height;
-}
-
-export function getTotalNotes(item) {
- if (!item || (item.type !== "notebook" && item.type !== "topic")) return 0;
- if (item.type === "topic") {
- return (
- db.notebooks.notebook(item.notebookId)?.topics.topic(item.id)
- ?.totalNotes || 0
- );
- }
- return db.notebooks.notebook(item.id)?.totalNotes || 0;
-}
-
-export async function toTXT(note, template = true) {
- let text;
- if (note.locked) {
- text = await db.notes.note(note.id).export("txt", note.content, template);
- } else {
- text = await db.notes.note(note.id).export("txt", undefined, template);
- }
- return text;
-}
-
-export function showTooltip(event, text, position = 2) {
- if (!event._targetInst?.ref?.current) return;
- prevTarget && RNTooltips.Dismiss(prevTarget);
- prevTarget = null;
- prevTarget = event._targetInst.ref.current;
- RNTooltips.Show(prevTarget, tabBarRef.current?.node?.current, {
- text: text,
- tintColor: "#000000",
- corner: Platform.OS === "ios" ? 5 : 40,
- textSize: 14,
- position: position,
- duration: 1000,
- autoHide: true,
- clickToHide: true
- });
-}
-
-export function toTitleCase(value) {
- if (!value) return;
- return value.slice(0, 1).toUpperCase() + value.slice(1);
-}
diff --git a/apps/mobile/app/utils/note-to-text.ts b/apps/mobile/app/utils/note-to-text.ts
new file mode 100644
index 000000000..1fb5ebe82
--- /dev/null
+++ b/apps/mobile/app/utils/note-to-text.ts
@@ -0,0 +1,32 @@
+/*
+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 { db } from "../common/database";
+import { NoteType } from "./types";
+
+export async function convertNoteToText(note: NoteType, template = true) {
+ let text;
+ if (note.locked) {
+ text = await db.notes
+ ?.note(note.id)
+ .export("txt", note.content as unknown as string, template);
+ } else {
+ text = await db.notes?.note(note.id).export("txt", undefined, template);
+ }
+ return text;
+}
diff --git a/apps/mobile/app/utils/notesnook-module.ts b/apps/mobile/app/utils/notesnook-module.ts
new file mode 100644
index 000000000..a2ceb7202
--- /dev/null
+++ b/apps/mobile/app/utils/notesnook-module.ts
@@ -0,0 +1,35 @@
+/*
+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 { NativeModules, Platform } from "react-native";
+
+interface NotesnookModuleInterface {
+ getActivityName: () => Promise;
+ setBackgroundColor: (color: string) => void;
+ setSecureMode: (enabled: boolean) => void;
+}
+
+export const NotesnookModule: NotesnookModuleInterface = Platform.select({
+ ios: {
+ getActivityName: () => {},
+ setBackgroundColor: () => {},
+ setSecureMode: () => {}
+ },
+ android: NativeModules.NNativeModule
+});
diff --git a/apps/mobile/app/utils/time/index.ts b/apps/mobile/app/utils/time/index.ts
index 5f2802d61..f32983d9e 100644
--- a/apps/mobile/app/utils/time/index.ts
+++ b/apps/mobile/app/utils/time/index.ts
@@ -17,115 +17,5 @@ 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));
-
-export function timeSince(date: number) {
- const seconds = Math.floor((Date.now() - date) / 1000);
- let interval = Math.floor(seconds / 31536000);
-
- if (interval > 0.9) {
- return interval < 2 ? interval + "yr ago" : interval + "yr ago";
- }
- interval = Math.floor(seconds / 2592000);
- if (interval > 0.9) {
- return interval < 2 ? interval + "mo ago" : interval + "mo ago";
- }
-
- interval = Math.floor(seconds / (86400 * 7));
- if (interval > 0.9) {
- if (interval === 4) return "1mo ago";
- return interval < 2 ? interval + "w ago" : interval + "w ago";
- }
-
- interval = Math.floor(seconds / 86400);
- if (interval > 0.9) {
- return interval < 2 ? interval + "d ago" : interval + "d ago";
- }
- interval = Math.floor(seconds / 3600);
- if (interval > 0.9) {
- return interval < 2 ? interval + "h ago" : interval + "h ago";
- }
- interval = Math.floor(seconds / 60);
- if (interval > 0.9) {
- return interval < 2 ? interval + "m ago" : interval + "m ago";
- }
- return Math.floor(seconds) < 0 ? "0s ago" : Math.floor(seconds) + "s ago";
-}
-
-export const timeConverter = (timestamp: number | undefined | null) => {
- if (!timestamp) return;
- const d = new Date(timestamp); // Convert the passed timestamp to milliseconds
- const yyyy = d.getFullYear();
- const dd = ("0" + d.getDate()).slice(-2); // Add leading 0.
- const currentDay = d.getDay();
- const hh = d.getHours();
- let h = hh;
- const min = ("0" + d.getMinutes()).slice(-2); // Add leading 0.
- let ampm = "AM";
- const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
- const months = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec"
- ];
-
- if (hh > 12) {
- h = hh - 12;
- ampm = "PM";
- } else if (hh === 12) {
- h = 12;
- ampm = "PM";
- } else if (hh === 0) {
- h = 12;
- }
-
- // ie: 2013-02-18, 8:35 AM
- const time =
- days[currentDay] +
- " " +
- dd +
- " " +
- months[d.getMonth()] +
- ", " +
- yyyy +
- ", " +
- h +
- ":" +
- min +
- " " +
- ampm;
-
- 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/apps/mobile/app/utils/tooltip.ts b/apps/mobile/app/utils/tooltip.ts
new file mode 100644
index 000000000..586d304d5
--- /dev/null
+++ b/apps/mobile/app/utils/tooltip.ts
@@ -0,0 +1,53 @@
+/*
+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 RNTooltips from "react-native-tooltips";
+import { tabBarRef } from "./global-refs";
+import { Platform } from "react-native";
+
+export const POSITIONS = {
+ LEFT: 1,
+ RIGHT: 2,
+ TOP: 3,
+ BOTTOM: 4
+};
+
+let prevTarget: any = null;
+function show(event: any, text: string, position = 2) {
+ if (!event._targetInst?.ref?.current) return;
+ prevTarget && RNTooltips.Dismiss(prevTarget);
+ prevTarget = null;
+ prevTarget = event._targetInst.ref.current;
+ RNTooltips.Show(prevTarget, tabBarRef.current?.node?.current, {
+ text: text,
+ tintColor: "#000000",
+ corner: Platform.OS === "ios" ? 5 : 40,
+ textSize: 14,
+ position: position,
+ duration: 1000,
+ autoHide: true,
+ clickToHide: true
+ });
+}
+
+const NativeTooltip = {
+ show,
+ POSITIONS
+};
+
+export default NativeTooltip;
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 72ac89ec2..4630bdd24 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -33,6 +33,7 @@
"@notesnook/editor": "*",
"@notesnook/editor-mobile": "*",
"react-native-swiper-flatlist": "3.2.2",
- "@notesnook/logger": "*"
+ "@notesnook/logger": "*",
+ "@notesnook/common": "*"
}
}
diff --git a/apps/mobile/patches/react-native-reanimated-material-menu+2.0.0.patch b/apps/mobile/patches/react-native-reanimated-material-menu+2.0.0.patch
index 09f19c2ac..e5d574c12 100644
--- a/apps/mobile/patches/react-native-reanimated-material-menu+2.0.0.patch
+++ b/apps/mobile/patches/react-native-reanimated-material-menu+2.0.0.patch
@@ -274,10 +274,10 @@ index 69065f0..0000000
-export default forwardRef(Menu);
diff --git a/node_modules/react-native-reanimated-material-menu/src/Menu.tsx b/node_modules/react-native-reanimated-material-menu/src/Menu.tsx
new file mode 100644
-index 0000000..20469ee
+index 0000000..c3bc1c9
--- /dev/null
+++ b/node_modules/react-native-reanimated-material-menu/src/Menu.tsx
-@@ -0,0 +1,289 @@
+@@ -0,0 +1,303 @@
+import React from 'react';
+
+import {
@@ -288,6 +288,7 @@ index 0000000..20469ee
+ LayoutChangeEvent,
+ Modal,
+ Platform,
++ ScrollView,
+ StatusBar,
+ StyleSheet,
+ TouchableWithoutFeedback,
@@ -325,6 +326,7 @@ index 0000000..20469ee
+
+const EASING = Easing.bezier(0.4, 0, 0.2, 1);
+const SCREEN_INDENT = 8;
++const SCREEN_INDENT_VERTICAL = 80;
+
+class Menu extends React.Component {
+ _container: View | null = null;
@@ -384,7 +386,7 @@ index 0000000..20469ee
+ }
+
+ const { width, height } = e.nativeEvent.layout;
-+
++ let timeout:any = 0;
+ this.setState(
+ {
+ menuState: States.Animating,
@@ -405,7 +407,17 @@ index 0000000..20469ee
+ easing: EASING,
+ useNativeDriver: false,
+ }),
-+ ]).start();
++ ]).start(({finished}) => {
++ if (finished) {
++ clearTimeout(timeout);
++ timeout = setTimeout(() => {
++ this.setState({
++ menuState: States.Shown
++ })
++ },20)
++
++ }
++ });
+ },
+ );
+ };
@@ -464,7 +476,7 @@ index 0000000..20469ee
+
+ // Adjust position of menu
+ let { left, top } = this.state;
-+ const transforms = [];
++ const transforms:any[] = [];
+
+ if (
+ (isRTL && left + buttonWidth - menuWidth > SCREEN_INDENT) ||
@@ -472,7 +484,7 @@ index 0000000..20469ee
+ ) {
+ transforms.push({
+ translateX: Animated.multiply(menuSizeAnimation.x, -1),
-+ });
++ } as never);
+
+ left = Math.min(windowWidth - SCREEN_INDENT, left + buttonWidth);
+ } else if (left < SCREEN_INDENT) {
@@ -480,26 +492,26 @@ index 0000000..20469ee
+ }
+
+ // Flip by Y axis if menu hits bottom screen border
-+ if (top > windowHeight - menuHeight - SCREEN_INDENT) {
++ if (top > windowHeight - menuHeight - SCREEN_INDENT_VERTICAL) {
+ transforms.push({
+ translateY: Animated.multiply(menuSizeAnimation.y, -1),
-+ });
++ } as never);
+
-+ top = windowHeight - SCREEN_INDENT;
-+ top = Math.min(windowHeight - SCREEN_INDENT, top + buttonHeight);
-+ } else if (top < SCREEN_INDENT) {
-+ top = SCREEN_INDENT;
++ top = windowHeight - SCREEN_INDENT_VERTICAL;
++ top = Math.min(windowHeight - SCREEN_INDENT_VERTICAL, top + buttonHeight);
++ } else if (top < SCREEN_INDENT_VERTICAL) {
++ top = SCREEN_INDENT_VERTICAL;
+ }
+
+ const shadowMenuContainerStyle = {
+ opacity: opacityAnimation,
+ transform: transforms,
++ maxHeight: 500,
+ top,
+
+ // Switch left to right for rtl devices
+ ...(isRTL ? { right: left } : { left }),
+ };
-+
+ const { menuState } = this.state;
+ const animationStarted = menuState === States.Animating;
+ const modalVisible = menuState === States.Shown || animationStarted;
@@ -529,7 +541,9 @@ index 0000000..20469ee
+ style={[styles.shadowMenuContainer, shadowMenuContainerStyle, style]}
+ >
+
-+ {children}
++
++ {children}
++
+
+
+
@@ -568,3 +582,19 @@ index 0000000..20469ee
+
+export default Menu
\ No newline at end of file
+diff --git a/node_modules/react-native-reanimated-material-menu/src/MenuItem.js b/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
+index 120a870..d6459c0 100644
+--- a/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
++++ b/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
+@@ -14,6 +14,11 @@ const Touchable =
+ ? TouchableNativeFeedback
+ : TouchableHighlight;
+
++/**
++ *
++ * @param {any} param0
++ * @returns
++ */
+ function MenuItem({
+ children,
+ disabled,
diff --git a/apps/mobile/share/search.js b/apps/mobile/share/search.js
index 184e9f0cb..ec41b1a9f 100644
--- a/apps/mobile/share/search.js
+++ b/apps/mobile/share/search.js
@@ -31,7 +31,7 @@ import {
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../app/common/database";
-import { getElevation } from "../app/utils";
+import { getElevationStyle } from "../app/utils/elevation";
import { initDatabase, useShareStore } from "./store";
const ListItem = ({ item, mode, close }) => {
@@ -262,7 +262,7 @@ export const Search = ({ close, getKeyboardHeight, quicknote, mode }) => {
alignSelf: "center",
overflow: "hidden",
zIndex: 999,
- ...getElevation(quicknote ? 1 : 5),
+ ...getElevationStyle(quicknote ? 1 : 5),
...extra
}}
>
diff --git a/apps/mobile/share/share.js b/apps/mobile/share/share.js
index 58d6adebf..3d16ccbae 100644
--- a/apps/mobile/share/share.js
+++ b/apps/mobile/share/share.js
@@ -45,7 +45,8 @@ import { db } from "../app/common/database";
import { MMKV } from "../app/common/database/mmkv";
import Storage from "../app/common/database/storage";
import { eSendEvent } from "../app/services/event-manager";
-import { formatBytes, getElevation } from "../app/utils";
+import { getElevationStyle } from "../app/utils/elevation";
+import { formatBytes } from "@notesnook/common";
import { eOnLoadNote } from "../app/utils/events";
import { Editor } from "./editor";
import { Search } from "./search";
@@ -459,7 +460,7 @@ const ShareView = ({ quicknote = false }) => {
backgroundColor: colors.bg,
height: 50 + insets.top,
paddingTop: insets.top,
- ...getElevation(1),
+ ...getElevationStyle(1),
marginTop: -insets.top,
flexDirection: "row",
alignItems: "center",
diff --git a/apps/web/package.json b/apps/web/package.json
index b3686a358..515dda2cf 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -26,6 +26,7 @@
"@notesnook/logger": "*",
"@notesnook/streamable-fs": "*",
"@notesnook/theme": "*",
+ "@notesnook/common": "*",
"@notesnook/web-clipper": "*",
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
diff --git a/apps/web/src/common/attachments.ts b/apps/web/src/common/attachments.ts
index e0f54cc7f..d28dcfbc5 100644
--- a/apps/web/src/common/attachments.ts
+++ b/apps/web/src/common/attachments.ts
@@ -23,7 +23,7 @@ import { db } from "./db";
async function download(hash: string) {
const attachment = db.attachments?.attachment(hash);
if (!attachment) return;
- const downloadResult = await db.fs.downloadFile(
+ const downloadResult = await db.fs?.downloadFile(
attachment.metadata.hash,
attachment.metadata.hash,
attachment.chunkSize,
diff --git a/apps/web/src/components/attachment/index.tsx b/apps/web/src/components/attachment/index.tsx
index 436c6da34..f4deab84c 100644
--- a/apps/web/src/components/attachment/index.tsx
+++ b/apps/web/src/components/attachment/index.tsx
@@ -299,7 +299,7 @@ const AttachmentMenuItems: MenuItem[] = [
onClick: async ({ attachment, status }) => {
const isDownloading = status?.type === "download";
if (isDownloading) {
- await db.fs.cancel(attachment.metadata.hash, "download");
+ await db.fs?.cancel(attachment.metadata.hash, "download");
} else await saveAttachment(attachment.metadata.hash);
}
},
@@ -311,7 +311,7 @@ const AttachmentMenuItems: MenuItem[] = [
onClick: async ({ attachment, status }) => {
const isDownloading = status?.type === "upload";
if (isDownloading) {
- await db.fs.cancel(attachment.metadata.hash, "upload");
+ await db.fs?.cancel(attachment.metadata.hash, "upload");
} else
await reuploadAttachment(
attachment.metadata.type,
diff --git a/apps/web/src/utils/streams/attachment-stream.ts b/apps/web/src/utils/streams/attachment-stream.ts
index 9c17c526a..98fbdff47 100644
--- a/apps/web/src/utils/streams/attachment-stream.ts
+++ b/apps/web/src/utils/streams/attachment-stream.ts
@@ -31,7 +31,7 @@ export class AttachmentStream extends ReadableStream {
) {
if (signal)
signal.onabort = async () => {
- await db.fs.cancel(GROUP_ID, "download");
+ await db.fs?.cancel(GROUP_ID, "download");
};
let index = 0;
@@ -46,7 +46,7 @@ export class AttachmentStream extends ReadableStream {
onProgress && onProgress(index);
const attachment = attachments[index++];
- await db.fs.downloadFile(
+ await db.fs?.downloadFile(
GROUP_ID,
attachment.metadata.hash,
attachment.chunkSize,
diff --git a/apps/web/src/views/recovery.tsx b/apps/web/src/views/recovery.tsx
index f1788df4b..56b9ac3c6 100644
--- a/apps/web/src/views/recovery.tsx
+++ b/apps/web/src/views/recovery.tsx
@@ -361,7 +361,7 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
const user = await db.user?.getUser();
if (!user) throw new Error("User not authenticated");
- await db.storage.write(`_uk_@${user.email}@_k`, form.recoveryKey);
+ await db.storage?.write(`_uk_@${user.email}@_k`, form.recoveryKey);
await db.sync(true, true);
navigate("backup");
}}
diff --git a/packages/common/.gitignore b/packages/common/.gitignore
new file mode 100644
index 000000000..8951265c9
--- /dev/null
+++ b/packages/common/.gitignore
@@ -0,0 +1,32 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+.now
+
+__diff_output__
+
+dist
+public/workbox
+scripts/secrets
+test-results
\ No newline at end of file
diff --git a/packages/common/.npmignore b/packages/common/.npmignore
new file mode 100644
index 000000000..def683b4c
--- /dev/null
+++ b/packages/common/.npmignore
@@ -0,0 +1,3 @@
+*
+!dist/**/*
+!package.json
\ No newline at end of file
diff --git a/packages/common/README.md b/packages/common/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json
new file mode 100644
index 000000000..31c8b8dc3
--- /dev/null
+++ b/packages/common/package-lock.json
@@ -0,0 +1,169 @@
+{
+ "name": "@notesnook/common",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@notesnook/common",
+ "version": "1.0.0",
+ "license": "GPL-3.0-or-later",
+ "devDependencies": {
+ "@types/react": "18.2.9",
+ "react": "18.2.0",
+ "timeago.js": "4.0.2",
+ "typescript": "^4.8.2"
+ },
+ "peerDependencies": {
+ "@notesnook/core": "*",
+ "react": "*",
+ "timeago.js": "4.0.2"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
+ "dev": true
+ },
+ "node_modules/@types/react": {
+ "version": "18.2.9",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz",
+ "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==",
+ "dev": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "dev": true
+ },
+ "node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/timeago.js": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz",
+ "integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==",
+ "dev": true
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ }
+ },
+ "dependencies": {
+ "@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
+ "dev": true
+ },
+ "@types/react": {
+ "version": "18.2.9",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz",
+ "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "dev": true
+ },
+ "csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "timeago.js": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz",
+ "integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ }
+ }
+}
diff --git a/packages/common/package.json b/packages/common/package.json
new file mode 100644
index 000000000..2f9151bcf
--- /dev/null
+++ b/packages/common/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@notesnook/common",
+ "version": "1.0.0",
+ "description": "A set of common utilities shared across multiple Notesnook clients and services.",
+ "main": "dist/index.js",
+ "scripts": {
+ "pub": "tsc && np",
+ "build": "tsc"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/streetwriters/notesnook.git"
+ },
+ "keywords": [
+ "logger"
+ ],
+ "license": "GPL-3.0-or-later",
+ "bugs": {
+ "url": "https://github.com/streetwriters/notesnook/issues"
+ },
+ "homepage": "https://github.com/streetwriters/notesnook#readme",
+ "np": {
+ "tests": false,
+ "releaseDraft": false,
+ "message": "chore: bump version to %s"
+ },
+ "publishConfig": {
+ "registry": "https://npm.pkg.github.com",
+ "access": "restricted"
+ },
+ "devDependencies": {
+ "typescript": "^4.8.2",
+ "@notesnook/core": "*",
+ "timeago.js": "4.0.2",
+ "react": "18.2.0",
+ "@types/react": "18.2.9"
+ },
+ "peerDependencies": {
+ "@notesnook/core": "*",
+ "timeago.js": "4.0.2",
+ "react": "*"
+ }
+}
diff --git a/packages/common/src/database.ts b/packages/common/src/database.ts
new file mode 100644
index 000000000..93a8b87d0
--- /dev/null
+++ b/packages/common/src/database.ts
@@ -0,0 +1,24 @@
+/*
+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 .
+*/
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+//@ts-ignore
+import Database from "@notesnook/core/api/index";
+
+export const database = new Database();
diff --git a/packages/common/src/hooks/use-time-ago.ts b/packages/common/src/hooks/use-time-ago.ts
new file mode 100644
index 000000000..8bad90b13
--- /dev/null
+++ b/packages/common/src/hooks/use-time-ago.ts
@@ -0,0 +1,88 @@
+/*
+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 { useEffect, useState } from "react";
+import { TDate, format, register } from "timeago.js";
+const shortLocale: [string, string][] = [
+ ["now", "now"],
+ ["%ss", "in %ss"],
+ ["1m", "in 1m"],
+ ["%sm", "in %sm"],
+ ["1h", "in 1h"],
+ ["%sh", "in %sh"],
+ ["1d", "in 1d"],
+ ["%sd", "in %sd"],
+ ["1w", "in 1w"],
+ ["%sw", "in %sw"],
+ ["1mo", "in 1mo"],
+ ["%smo", "in %smo"],
+ ["1yr", "in 1yr"],
+ ["%syr", "in %syr"]
+];
+
+const enShortLocale: [string, string][] = [
+ ["now", "now"],
+ ["%ss ago", "in %ss"],
+ ["1m ago", "in 1m"],
+ ["%sm ago", "in %sm"],
+ ["1h ago", "in 1h"],
+ ["%sh ago", "in %sh"],
+ ["1d ago", "in 1d"],
+ ["%sd ago", "in %sd"],
+ ["1w ago", "in 1w"],
+ ["%sw ago", "in %sw"],
+ ["1mo ago", "in 1mo"],
+ ["%smo ago", "in %smo"],
+ ["1yr ago", "in 1yr"],
+ ["%syr ago", "in %syr"]
+];
+register("short", (_n, index) => shortLocale[index]);
+register("en_short", (_n, index) => enShortLocale[index]);
+
+export function getTimeAgo(datetime: TDate, locale = "short") {
+ return format(datetime, locale);
+}
+
+type TimeAgoOptions = {
+ locale?: "short" | "en_short";
+ live?: boolean;
+ interval?: number;
+ onUpdate?: (timeAgo: string) => void;
+};
+
+export function useTimeAgo(
+ datetime: TDate,
+ { locale = "short", live = true, interval = 60000, onUpdate }: TimeAgoOptions
+) {
+ const [timeAgo, setTimeAgo] = useState(getTimeAgo(datetime, locale));
+
+ useEffect(() => {
+ if (!live) return;
+ const reset = setInterval(() => {
+ const value = getTimeAgo(datetime, locale);
+ onUpdate?.(value);
+ setTimeAgo(value);
+ }, interval);
+ return () => {
+ clearInterval(reset);
+ };
+ }, [datetime, interval, locale, live, onUpdate]);
+
+ return timeAgo;
+}
diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts
new file mode 100644
index 000000000..33c0564df
--- /dev/null
+++ b/packages/common/src/index.ts
@@ -0,0 +1,27 @@
+/*
+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 .
+*/
+
+export * from "./database";
+
+export * from "./utils/file";
+export * from "./utils/number";
+export * from "./utils/total-notes";
+export * from "./utils/time";
+
+export * from "./hooks/use-time-ago";
diff --git a/apps/mobile/app/utils/sanitizer/index.ts b/packages/common/src/utils/file.ts
similarity index 86%
rename from apps/mobile/app/utils/sanitizer/index.ts
rename to packages/common/src/utils/file.ts
index 311540a1c..c9c183fd7 100644
--- a/apps/mobile/app/utils/sanitizer/index.ts
+++ b/packages/common/src/utils/file.ts
@@ -17,6 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+export function formatBytes(bytes: number, decimals = 2) {
+ if (bytes === 0) return "0 Bytes";
+
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
+}
+
/*jshint node:true*/
/**
@@ -69,7 +80,7 @@ function sanitize(input: string, replacement: string) {
export function sanitizeFilename(
input: string,
- options: { replacement: string }
+ options?: { replacement: string }
) {
const replacement = (options && options.replacement) || "";
return sanitize(input, replacement);
diff --git a/packages/common/src/utils/number.ts b/packages/common/src/utils/number.ts
new file mode 100644
index 000000000..d24b70bd9
--- /dev/null
+++ b/packages/common/src/utils/number.ts
@@ -0,0 +1,26 @@
+/*
+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 .
+*/
+
+export function nth(number: number) {
+ return (
+ ["st", "nd", "rd"][
+ (((((number < 0 ? -number : number) + 90) % 100) - 10) % 10) - 1
+ ] || "th"
+ );
+}
diff --git a/packages/common/src/utils/time.ts b/packages/common/src/utils/time.ts
new file mode 100644
index 000000000..9030b7921
--- /dev/null
+++ b/packages/common/src/utils/time.ts
@@ -0,0 +1,40 @@
+/*
+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 { formatDate } from "@notesnook/core/utils/date";
+import { database } from "../database";
+import { formatReminderTime } from "@notesnook/core/collections/reminders";
+
+export function getFormattedDate(
+ date: string | number | Date,
+ type: "time" | "date-time" | "date" = "date-time"
+) {
+ return formatDate(date, {
+ dateFormat: database.settings?.getDateFormat() as string,
+ timeFormat: database.settings?.getTimeFormat() as string,
+ type: type
+ });
+}
+
+export function getFormattedReminderTime(reminder: any, short = false) {
+ return formatReminderTime(reminder, short, {
+ dateFormat: database.settings?.getDateFormat() as string,
+ timeFormat: database.settings?.getTimeFormat() as string
+ });
+}
diff --git a/packages/common/src/utils/total-notes.ts b/packages/common/src/utils/total-notes.ts
new file mode 100644
index 000000000..0ce040ef7
--- /dev/null
+++ b/packages/common/src/utils/total-notes.ts
@@ -0,0 +1,30 @@
+/*
+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 { database } from "../database";
+
+export function getTotalNotes(item?: any) {
+ if (!item || (item.type !== "notebook" && item.type !== "topic")) return 0;
+ if (item.type === "topic") {
+ return (
+ database.notebooks?.notebook(item.notebookId)?.topics.topic(item.id)
+ ?.totalNotes || 0
+ );
+ }
+ return database.notebooks?.notebook(item.id)?.totalNotes || 0;
+}
diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json
new file mode 100644
index 000000000..80ac9524f
--- /dev/null
+++ b/packages/common/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "maxNodeModuleJsDepth": 10,
+ "downlevelIteration": true,
+ "allowJs": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/"]
+}