mobile: refactor

This commit is contained in:
ammarahm-ed
2023-06-10 12:17:23 +05:00
committed by Abdullah Atta
parent c881e74aae
commit fb616a246e
92 changed files with 1110 additions and 1078 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;

View File

@@ -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);

View File

@@ -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";

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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}
>

View File

@@ -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 }) => {
<View
{...restProps}
style={{
...getElevation(5),
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,

View File

@@ -25,7 +25,7 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { getElevation } from "../../utils";
import { getElevationStyle } from "../../utils/elevation";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import { Toast } from "../toast";
@@ -111,7 +111,7 @@ export const Dialog = ({ context = "global" }) => {
};
const style = {
...getElevation(5),
...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
borderRadius: 5,

View File

@@ -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 }) => {
>
<View
style={{
...getElevation(5),
...getElevationStyle(5),
width: DDS.isTab ? 500 : "85%",
backgroundColor: colors.bg,
zIndex: 100,

View File

@@ -25,7 +25,7 @@ import {
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useThemeStore } from "../../../stores/use-theme-store";
import { getElevation } from "../../../utils";
import { getElevationStyle } from "../../../utils/elevation";
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
@@ -68,7 +68,7 @@ const ResultDialog = () => {
<BaseDialog visible={true} onRequestClose={close}>
<View
style={{
...getElevation(5),
...getElevationStyle(5),
width: DDS.isTab ? 350 : "85%",
maxHeight: 500,
borderRadius: 10,

View File

@@ -34,7 +34,8 @@ import {
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { getElevation, toTXT } from "../../../utils";
import { convertNoteToText } from "../../../utils/note-to-text";
import { getElevationStyle } from "../../../utils/elevation";
import {
eClearEditor,
eCloseActionSheet,
@@ -564,7 +565,7 @@ export class VaultDialog extends Component {
}
async _copyNote(note) {
Clipboard.setString(await toTXT(note));
Clipboard.setString(await convertNoteToText(note));
ToastEvent.show({
heading: "Note copied",
type: "success",
@@ -580,7 +581,7 @@ export class VaultDialog extends Component {
await Share.open({
heading: "Share note",
failOnCancel: false,
message: await toTXT(note)
message: await convertNoteToText(note)
});
} catch (e) {
console.error(e);
@@ -678,7 +679,7 @@ export class VaultDialog extends Component {
>
<View
style={{
...getElevation(5),
...getElevationStyle(5),
width: DDS.isTab ? 350 : "85%",
borderRadius: 10,
backgroundColor: colors.bg,

View File

@@ -92,7 +92,7 @@ export const RightMenus = () => {
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
button={
<IconButton
onPress={() => {
menuRef.current?.show();

View File

@@ -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}

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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) {

View File

@@ -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);
};

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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 = () => {
<Animated.View
style={{
height: dHeight / 2 - (50 + insets.top / 2),
height: height / 2 - (50 + insets.top / 2),
backgroundColor: colors.bg,
borderBottomWidth: 1,
borderBottomColor: colors.nav
@@ -332,7 +334,7 @@ const MergeConflicts = () => {
<Animated.View
style={{
height: dHeight / 2 - (50 + insets.top / 2),
height: height / 2 - (50 + insets.top / 2),
backgroundColor: colors.bg,
borderRadius: 10
}}

View File

@@ -26,13 +26,13 @@ import { presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { getFormattedDate, timeSince } from "../../utils/time";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { PressableButton } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import NotePreview from "./preview";
import { getFormattedDate, getTimeAgo } from "@notesnook/common";
export default function NoteHistory({ note, fwdRef }) {
const [history, setHistory] = useState([]);
@@ -90,7 +90,7 @@ export default function NoteHistory({ note, fwdRef }) {
>
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{timeSince(item.dateModified)}
{getTimeAgo(item.dateModified)}
</Paragraph>
</PressableButton>
),

View File

@@ -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}

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
},

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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([]);
},

View File

@@ -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" }) => {
>
<View
style={{
...getElevation(5),
...getElevationStyle(5),
maxWidth: "95%",
backgroundColor: colors.nav,
minWidth: data?.func ? "95%" : "50%",

View File

@@ -27,9 +27,9 @@ import Animated, {
} from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { ColorKey, useThemeStore } from "../../../stores/use-theme-store";
import { showTooltip, TOOLTIP_POSITIONS } from "../../../utils";
import { BUTTON_TYPES } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
import NativeTooltip from "../../../utils/tooltip";
import { ButtonProps } from "../button";
import { PressableButton } from "../pressable";
import Heading from "../typography/heading";
@@ -86,7 +86,7 @@ export const AnimatedButton = ({
return;
}
if (tooltipText) {
showTooltip(event, tooltipText, TOOLTIP_POSITIONS.TOP);
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading}

View File

@@ -22,15 +22,15 @@ import {
ActivityIndicator,
ColorValue,
TextStyle,
ViewStyle,
View
View,
ViewStyle
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { ColorKey, useThemeStore } from "../../../stores/use-theme-store";
import { useUserStore } from "../../../stores/use-user-store";
import { showTooltip, TOOLTIP_POSITIONS } from "../../../utils";
import { BUTTON_TYPES } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
import NativeTooltip from "../../../utils/tooltip";
import { ProTag } from "../../premium/pro-tag";
import { PressableButton, PressableButtonProps } from "../pressable";
import Heading from "../typography/heading";
@@ -105,7 +105,7 @@ export const Button = ({
return;
}
if (tooltipText) {
showTooltip(event, tooltipText, TOOLTIP_POSITIONS.TOP);
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading}

View File

@@ -22,10 +22,10 @@ import { ColorValue, GestureResponderEvent, ViewStyle } from "react-native";
import Animated, { Layout } from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { ColorKey, useThemeStore } from "../../../stores/use-theme-store";
import { showTooltip, TOOLTIP_POSITIONS } from "../../../utils";
import { hexToRGBA, RGB_Linear_Shade } from "../../../utils/color-scheme/utils";
import { SIZE } from "../../../utils/size";
import { PressableButton, PressableButtonProps } from "../pressable";
import NativeTooltip from "../../../utils/tooltip";
interface IconButtonProps extends PressableButtonProps {
name: string;
color?: ColorValue;
@@ -56,7 +56,7 @@ export const IconButton = ({
tooltipText,
type = "gray",
fwdRef,
tooltipPosition = TOOLTIP_POSITIONS.TOP,
tooltipPosition = NativeTooltip.POSITIONS.TOP,
...restProps
}: IconButtonProps) => {
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);
}
};

View File

@@ -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
}}
>

View File

@@ -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,

View File

@@ -17,9 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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<string | null>(null);
const interval = useRef<NodeJS.Timer>();
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 ? (
<Heading style={style}>{timeAgo}</Heading>

View File

@@ -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
}}

View File

@@ -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, "<br />");
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)
});
}
}

View File

@@ -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();

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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",

View File

@@ -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
}}

View File

@@ -17,107 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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 (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
await PremiumService.verify(menuRef.current?.show);
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>{settings.homepage}</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{MenuItemsList.slice(0, MenuItemsList.length - 1).map(
(item) =>
item.name !== "Monographs" && (
<MenuItem
key={item.name}
onPress={async () => {
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}
</MenuItem>
)
)}
</Menu>
</View>
);
};
export const AccentColorPicker = () => {
const colors = useThemeStore((state) => state.colors);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 (
<View
style={{
flexDirection: "row",
borderRadius: 5,
overflow: "hidden",
flexShrink: 1,
width: "100%"
}}
>
{[
{
title: "Never",
value: "useroff"
},
{
title: "Daily",
value: "daily"
},
{
title: "Weekly",
value: "weekly"
},
{
title: "Monthly",
value: "monthly"
}
].map((item, index) => (
<TouchableOpacity
activeOpacity={0.9}
onPress={async () => {
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
}}
>
<Paragraph
color={settings.reminder === item.value ? "white" : colors.icon}
size={SIZE.sm - 1}
>
{item.title}
</Paragraph>
</TouchableOpacity>
))}
</View>
);
};

View File

@@ -18,29 +18,34 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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: <AccentColorPicker />,
homeselector: <HomagePageSelector />,
autobackups: <AutomaticBackupsSelector />,
homeselector: <HomePicker />,
autobackups: <BackupReminderPicker />,
subscription: <Subscription />,
configuretoolbar: <ConfigureToolbar />,
"debug-logs": <DebugLogs />,
"sound-picker": <SoundPicker />,
licenses: <Licenses />,
"trash-interval-selector": <TrashIntervalSelector />,
"font-selector": <FontSelector />,
"trash-interval-selector": <TrashIntervalPicker />,
"font-selector": <FontPicker />,
"title-format": <TitleFormat />,
"date-format-selector": <DateFormatSelector />,
"time-format-selector": <TimeFormatSelector />
"date-format-selector": <DateFormatPicker />,
"time-format-selector": <TimeFormatPicker />
};

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
import dayjs from "dayjs";
import React, { useRef, useState } from "react";
import { View } from "react-native";
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { PressableButton } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common";
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 (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{dateFormat} ({dayjs().format(dateFormat)})
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{DATE_FORMATS.map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
onChange(item);
}}
style={{
backgroundColor: dateFormat === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: dateFormat === item ? colors.accent : colors.pri
}}
>
{item} ({dayjs().format(item)})
</MenuItem>
))}
</Menu>
</View>
);
};
export const TimeFormatSelector = () => {
const colors = useThemeStore((state) => state.colors);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const [timeFormat, setTimeFormat] = useState(db.settings.getTimeFormat());
const onChange = (item) => {
menuRef.current?.hide();
db.settings.setTimeFormat(item);
setTimeFormat(item);
useSettingStore.setState({
timeFormat: item
});
};
const TimeFormats = {
"12-hour": "hh:mm A",
"24-hour": "HH:mm"
};
return (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{timeFormat} ({dayjs().format(TimeFormats[timeFormat])})
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{TIME_FORMATS.map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
onChange(item);
}}
style={{
backgroundColor: timeFormat === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: timeFormat === item ? colors.accent : colors.pri
}}
>
{item} ({dayjs().format(TimeFormats[item])})
</MenuItem>
))}
</Menu>
</View>
);
};

View File

@@ -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:

View File

@@ -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
}
]}

View File

@@ -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)
}}
>
<View

View File

@@ -1,110 +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 <http://www.gnu.org/licenses/>.
*/
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 (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>{getFontById(defaultFontFamily).title}</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{getFonts().map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
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}
</MenuItem>
))}
</Menu>
</View>
);
};

View File

@@ -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<T> {
getValue: () => T;
updateValue: (item: T) => Promise<void>;
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<T>({
getValue,
updateValue,
formatValue,
compareValue,
options,
getItemKey,
premium,
onCheckOptionIsPremium = () => true
}: PickerOptions<T>) {
const colors = useThemeStore((state) => state.colors);
const [trashInterval, setTrashInterval] = useState(
db.settings.getTrashCleanupInterval()
);
const menuRef = useRef();
const menuRef = useRef<any>();
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
}}
>
<Paragraph>
{trashInterval === -1 ? "Never" : trashInterval + " days"}
</Paragraph>
<Paragraph>{formatValue(currentValue)}</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{[-1, 7, 30, 365].map((item) => (
{options.map((item) => (
<MenuItem
key={item.toString()}
key={getItemKey(item)}
onPress={async () => {
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)}
</MenuItem>
))}
</Menu>
</View>
);
};
}
export function createSettingsPicker<T>(props: PickerOptions<T>) {
const Selector = () => {
return <SettingsPicker {...props} />;
};
return Selector;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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"
});

View File

@@ -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 });

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import {
beginBackgroundTask,
endBackgroundTask
} from "react-native-begin-background-task";
async function doInBackground(callback: () => Promise<void>) {
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 <http://www.gnu.org/licenses/>.
// start,
// registerHeadlessTask
// };
export {};
export default { doInBackground };

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -17,15 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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;

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<unknown>) => void;
setSelectionMode: (mode: boolean) => void;
setSelectedItem: (item: Item) => void;
clearSelection: (noanimation: boolean) => void;
clearSelection: () => void;
}
export const useSelectionStore = create<SelectionStore>((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<SelectionStore>((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: [] });
}
}));

View File

@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@@ -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"
};

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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
};
}

View File

@@ -18,18 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
import { NativeModules, Platform } from "react-native";
interface NotesnookModuleInterface {
getActivityName: () => Promise<string>;
setBackgroundColor: (color: string) => void;
setSecureMode: (enabled: boolean) => void;
}
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
ios: {
getActivityName: () => {},
setBackgroundColor: () => {},
setSecureMode: () => {}
},
android: NativeModules.NNativeModule
});

View File

@@ -17,115 +17,5 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatDate } from "@notesnook/core/utils/date";
import { db } from "../../common/database";
import { formatReminderTime } from "@notesnook/core/collections/reminders";
export const sleep = (duration: number) =>
new Promise((resolve) => setTimeout(() => resolve(true), duration));
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
});
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;

View File

@@ -33,6 +33,7 @@
"@notesnook/editor": "*",
"@notesnook/editor-mobile": "*",
"react-native-swiper-flatlist": "3.2.2",
"@notesnook/logger": "*"
"@notesnook/logger": "*",
"@notesnook/common": "*"
}
}

View File

@@ -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<MenuProps, State> {
+ _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]}
+ >
+ <Animated.View style={[styles.menuContainer, animationStarted && menuSize]}>
+ {children}
+ <ScrollView showsVerticalScrollIndicator={menuState !== States.Animating} >
+ {children}
+ </ScrollView>
+ </Animated.View>
+ </Animated.View>
+ </View>
@@ -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,

View File

@@ -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
}}
>

View File

@@ -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",

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,

View File

@@ -31,7 +31,7 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
) {
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<ZipFile> {
onProgress && onProgress(index);
const attachment = attachments[index++];
await db.fs.downloadFile(
await db.fs?.downloadFile(
GROUP_ID,
attachment.metadata.hash,
attachment.chunkSize,

View File

@@ -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");
}}

32
packages/common/.gitignore vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,3 @@
*
!dist/**/*
!package.json

View File

169
packages/common/package-lock.json generated Normal file
View File

@@ -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
}
}
}

View File

@@ -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": "*"
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
import Database from "@notesnook/core/api/index";
export const database = new Database();

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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";

View File

@@ -17,6 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
export function nth(number: number) {
return (
["st", "nd", "rd"][
(((((number < 0 ? -number : number) + 90) % 100) - 10) % 10) - 1
] || "th"
);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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
});
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist",
"maxNodeModuleJsDepth": 10,
"downlevelIteration": true,
"allowJs": true,
"jsx": "react-jsx"
},
"include": ["src/"]
}