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%" - }} - > - { - menuRef.current?.hide(); - }} - anchor={ - { - await PremiumService.verify(menuRef.current?.show); - }} - type="grayBg" - customStyle={{ - flexDirection: "row", - alignItems: "center", - marginTop: 10, - width: "100%", - justifyContent: "space-between", - padding: 12 - }} - > - {settings.homepage} - - - } - > - {MenuItemsList.slice(0, MenuItemsList.length - 1).map( - (item) => - item.name !== "Monographs" && ( - { - menuRef.current?.hide(); - await SettingsService.set({ homepage: item.name }); - ToastEvent.show({ - heading: "Homepage set to " + item.name, - message: "Restart the app for changes to take effect.", - type: "success" - }); - }} - style={{ - backgroundColor: - settings.homepage === item.name - ? colors.nav - : "transparent", - width: "100%", - maxWidth: width - }} - textStyle={{ - fontSize: SIZE.md, - color: - settings.homepage === item.name ? colors.accent : colors.pri - }} - > - {item.name} - - ) - )} - - - ); -}; 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%" - }} - > - { - menuRef.current?.hide(); - }} - anchor={ - { - menuRef.current?.show(); - }} - type="grayBg" - customStyle={{ - flexDirection: "row", - alignItems: "center", - marginTop: 10, - width: "100%", - justifyContent: "space-between", - padding: 12 - }} - > - - {dateFormat} ({dayjs().format(dateFormat)}) - - - - } - > - {DATE_FORMATS.map((item) => ( - { - onChange(item); - }} - style={{ - backgroundColor: dateFormat === item ? colors.nav : "transparent", - width: "100%", - maxWidth: width - }} - textStyle={{ - fontSize: SIZE.md, - color: dateFormat === item ? colors.accent : colors.pri - }} - > - {item} ({dayjs().format(item)}) - - ))} - - - ); -}; - -export const TimeFormatSelector = () => { - const colors = useThemeStore((state) => state.colors); - const menuRef = useRef(); - const [width, setWidth] = useState(0); - const [timeFormat, setTimeFormat] = useState(db.settings.getTimeFormat()); - const onChange = (item) => { - menuRef.current?.hide(); - db.settings.setTimeFormat(item); - setTimeFormat(item); - useSettingStore.setState({ - timeFormat: item - }); - }; - - const TimeFormats = { - "12-hour": "hh:mm A", - "24-hour": "HH:mm" - }; - - return ( - { - setWidth(event.nativeEvent.layout.width); - }} - style={{ - width: "100%" - }} - > - { - menuRef.current?.hide(); - }} - anchor={ - { - menuRef.current?.show(); - }} - type="grayBg" - customStyle={{ - flexDirection: "row", - alignItems: "center", - marginTop: 10, - width: "100%", - justifyContent: "space-between", - padding: 12 - }} - > - - {timeFormat} ({dayjs().format(TimeFormats[timeFormat])}) - - - - } - > - {TIME_FORMATS.map((item) => ( - { - onChange(item); - }} - style={{ - backgroundColor: timeFormat === item ? colors.nav : "transparent", - width: "100%", - maxWidth: width - }} - textStyle={{ - fontSize: SIZE.md, - color: timeFormat === item ? colors.accent : colors.pri - }} - > - {item} ({dayjs().format(TimeFormats[item])}) - - ))} - - - ); -}; diff --git a/apps/mobile/app/screens/settings/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%" - }} - > - { - menuRef.current?.hide(); - }} - anchor={ - { - menuRef.current?.show(); - }} - type="grayBg" - customStyle={{ - flexDirection: "row", - alignItems: "center", - marginTop: 10, - width: "100%", - justifyContent: "space-between", - padding: 12 - }} - > - {getFontById(defaultFontFamily).title} - - - } - > - {getFonts().map((item) => ( - { - onChange(item.id); - }} - style={{ - backgroundColor: - defaultFontFamily === item.id ? colors.nav : "transparent", - width: "100%", - maxWidth: width - }} - textStyle={{ - fontSize: SIZE.md, - color: defaultFontFamily === item.id ? colors.accent : colors.pri - }} - > - {item.title} - - ))} - - - ); -}; 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) => ( { - if (item === -1) { - await PremiumService.verify(() => { - onChange(item); - }); - return; - } onChange(item); }} style={{ - backgroundColor: - trashInterval === item ? colors.nav : "transparent", + backgroundColor: compareValue(currentValue, item) + ? colors.nav + : "transparent", width: "100%", maxWidth: width }} textStyle={{ fontSize: SIZE.md, - color: trashInterval === item ? colors.accent : colors.pri + color: compareValue(currentValue, item) + ? colors.accent + : colors.pri }} > - {item === -1 ? "Never" : item + " days"} + {formatValue(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/"] +}