mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
mobile: migrate to typescript
This commit is contained in:
committed by
Abdullah Atta
parent
9ef40c01ce
commit
1f4cd6504a
@@ -17,6 +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 "@azure/core-asynciterator-polyfill";
|
||||
import "react-native-gesture-handler";
|
||||
import {
|
||||
THEME_COMPATIBILITY_VERSION,
|
||||
useThemeEngineStore
|
||||
@@ -42,11 +43,10 @@ I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
|
||||
SettingsService.checkOrientation();
|
||||
const App = () => {
|
||||
const init = useAppEvents();
|
||||
useEffect(() => {
|
||||
let { appLockMode } = SettingsService.get();
|
||||
const { appLockMode } = SettingsService.get();
|
||||
if (appLockMode && appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
@@ -101,10 +101,10 @@ const App = () => {
|
||||
let currTheme =
|
||||
useThemeStore.getState().colorScheme === "dark"
|
||||
? SettingsService.getProperty("darkTheme")
|
||||
: SettingsService.getProperty("lightTheme");
|
||||
: SettingsService.getProperty("lighTheme");
|
||||
useThemeEngineStore.getState().setTheme(currTheme);
|
||||
|
||||
export const withTheme = (Element) => {
|
||||
export const withTheme = (Element: () => JSX.Element) => {
|
||||
return function AppWithThemeProvider() {
|
||||
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
|
||||
state.colorScheme,
|
||||
@@ -16,6 +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 "./logger";
|
||||
import { database } from "@notesnook/common";
|
||||
import { logger as dbLogger } from "@notesnook/core/dist/logger";
|
||||
import { Platform } from "react-native";
|
||||
@@ -23,7 +24,7 @@ import * as Gzip from "react-native-gzip";
|
||||
import EventSource from "../../utils/sse/even-source-ios";
|
||||
import AndroidEventSource from "../../utils/sse/event-source";
|
||||
import filesystem from "../filesystem";
|
||||
import "./logger";
|
||||
|
||||
import Storage from "./storage";
|
||||
|
||||
database.host(
|
||||
@@ -49,15 +50,15 @@ database.host(
|
||||
}
|
||||
);
|
||||
|
||||
database.setup(
|
||||
Storage,
|
||||
Platform.OS === "ios" ? EventSource : AndroidEventSource,
|
||||
filesystem,
|
||||
{
|
||||
database.setup({
|
||||
storage: Storage,
|
||||
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,
|
||||
filesystem: filesystem,
|
||||
compressor: {
|
||||
compress: Gzip.deflate,
|
||||
decompress: Gzip.inflate
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const db = database;
|
||||
export const DatabaseLogger = dbLogger;
|
||||
|
||||
@@ -29,7 +29,7 @@ import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { subscribe, zip } from "react-native-zip-archive";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { ToastEvent, presentSheet } from "../../services/event-manager";
|
||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
@@ -254,7 +254,7 @@ export default async function downloadAttachment(
|
||||
);
|
||||
|
||||
if (!options.silent) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Download successful",
|
||||
message: filename + " downloaded",
|
||||
type: "success"
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import hosts from "@notesnook/core/dist/utils/constants";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
@@ -74,13 +74,13 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
return status >= 200 && status < 300;
|
||||
} catch (e) {
|
||||
if (e.message !== "canceled") {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -18,7 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { Platform, TextInput, View } from "react-native";
|
||||
//@ts-ignore
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { db } from "../../common/database";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
@@ -41,8 +42,8 @@ const AppLockedOverlay = () => {
|
||||
const appLocked = useUserStore((state) => state.appLocked);
|
||||
const lockApp = useUserStore((state) => state.lockApp);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const passwordInputRef = useRef();
|
||||
const password = useRef();
|
||||
const passwordInputRef = useRef<TextInput>(null);
|
||||
const password = useRef<string>();
|
||||
const appState = useAppState();
|
||||
|
||||
const biometricUnlockAwaitingUserInput = useRef(false);
|
||||
@@ -55,14 +56,14 @@ const AppLockedOverlay = () => {
|
||||
}
|
||||
useSettingStore.getState().setRequestBiometrics(true);
|
||||
|
||||
let unlocked = await BiometricService.validateUser(
|
||||
const unlocked = await BiometricService.validateUser(
|
||||
"Unlock to access your notes",
|
||||
""
|
||||
);
|
||||
if (unlocked) {
|
||||
lockApp(false);
|
||||
enabled(false);
|
||||
password.current = null;
|
||||
password.current = undefined;
|
||||
}
|
||||
setTimeout(() => {
|
||||
biometricUnlockAwaitingUserInput.current = false;
|
||||
@@ -73,11 +74,11 @@ const AppLockedOverlay = () => {
|
||||
const onSubmit = async () => {
|
||||
if (!password.current) return;
|
||||
try {
|
||||
let unlocked = await db.user.verifyPassword(password.current);
|
||||
const unlocked = await db.user.verifyPassword(password.current);
|
||||
if (unlocked) {
|
||||
lockApp(false);
|
||||
enabled(false);
|
||||
password.current = null;
|
||||
password.current = undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -170,7 +171,9 @@ const AppLockedOverlay = () => {
|
||||
secureTextEntry
|
||||
placeholder="Enter account password"
|
||||
onChangeText={(v) => (password.current = v)}
|
||||
onSubmit={onSubmit}
|
||||
onSubmit={() => {
|
||||
onSubmit();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
@@ -29,7 +29,7 @@ import picker from "../../screens/editor/tiptap/picker";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
@@ -76,7 +76,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
name: "Reupload",
|
||||
onPress: async () => {
|
||||
if (!PremiumService.get()) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Upgrade to pro",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -102,7 +102,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
if (res.failed) {
|
||||
db.attachments.markAsFailed(attachment.id, res.failed);
|
||||
setFailed(res.failed);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "File check failed with error: " + res.failed,
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -110,7 +110,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
} else {
|
||||
setFailed(null);
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "File check passed",
|
||||
type: "success",
|
||||
context: "local"
|
||||
@@ -248,7 +248,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Clipboard.setString(attachment.metadata.hash);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
type: "success",
|
||||
heading: "Attachment hash copied",
|
||||
context: "local"
|
||||
@@ -287,7 +287,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
if (item.type === "notfound") {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note not found",
|
||||
message:
|
||||
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
|
||||
@@ -23,7 +23,7 @@ import { db } from "../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
|
||||
@@ -48,7 +48,7 @@ export const ChangePassword = () => {
|
||||
|
||||
const changePassword = async () => {
|
||||
if (!user?.isEmailConfirmed) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Email not confirmed",
|
||||
message: "Please confirm your email to change account password",
|
||||
type: "error",
|
||||
@@ -57,7 +57,7 @@ export const ChangePassword = () => {
|
||||
return;
|
||||
}
|
||||
if (error || !oldPassword.current || !password.current) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again.",
|
||||
type: "error",
|
||||
@@ -72,7 +72,7 @@ export const ChangePassword = () => {
|
||||
|
||||
await db.user.clearSessions();
|
||||
await db.user.changePassword(oldPassword.current, password.current);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Account password updated",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -83,7 +83,7 @@ export const ChangePassword = () => {
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Failed to change password",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
@@ -43,7 +43,7 @@ export const ForgotPassword = () => {
|
||||
|
||||
const sendRecoveryEmail = async () => {
|
||||
if (!email.current || error) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Account email is required.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -63,7 +63,7 @@ export const ForgotPassword = () => {
|
||||
SettingsService.set({
|
||||
lastRecoveryEmailTime: Date.now()
|
||||
});
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Check your email to reset password",
|
||||
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
|
||||
type: "success",
|
||||
@@ -74,7 +74,7 @@ export const ForgotPassword = () => {
|
||||
setSent(true);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery email not sent",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -24,7 +24,7 @@ import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
@@ -91,7 +91,7 @@ export const SessionExpired = () => {
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, { useRef, useState } from "react";
|
||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -52,7 +52,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
const { width, height } = useWindowDimensions();
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current || !confirmPassword.current) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again",
|
||||
type: "error",
|
||||
@@ -85,7 +85,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Signup failed",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -24,7 +24,7 @@ import useTimer from "../../hooks/use-timer";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
@@ -138,7 +138,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
setSending(false);
|
||||
} catch (e) {
|
||||
setSending(false);
|
||||
ToastEvent.error(e, "Error sending 2FA Code", "local");
|
||||
ToastManager.error(e, "Error sending 2FA Code", "local");
|
||||
}
|
||||
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import { clearMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
@@ -48,7 +48,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
(!password.current && step === LoginSteps.passwordAuth) ||
|
||||
(!email.current && step === LoginSteps.emailAuth)
|
||||
) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again",
|
||||
type: "error",
|
||||
@@ -121,7 +121,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
const finishWithError = async (e) => {
|
||||
if (e.message === "invalid_grant") setStep(LoginSteps.emailAuth);
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Login failed",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
@@ -135,7 +135,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
PremiumService.setPremiumStatus();
|
||||
setUser(user);
|
||||
clearMessage();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Login successful",
|
||||
message: `Logged in as ${user.email}`,
|
||||
type: "success",
|
||||
|
||||
@@ -36,10 +36,11 @@ import { SIZE, normalize } from "../../utils/size";
|
||||
import NativeTooltip from "../../utils/tooltip";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
|
||||
interface FloatingButton {
|
||||
title?: string;
|
||||
interface FloatingButtonProps {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
color?: string;
|
||||
shouldShow?: boolean;
|
||||
alwaysVisible?: boolean;
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ const FloatingButton = ({
|
||||
onPress,
|
||||
color,
|
||||
alwaysVisible = false
|
||||
}: FloatingButton) => {
|
||||
}: FloatingButtonProps) => {
|
||||
const { colors } = useThemeColors();
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
|
||||
@@ -17,8 +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 React, { PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ColorValue,
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
@@ -35,6 +36,25 @@ import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
|
||||
export interface BaseDialogProps extends PropsWithChildren {
|
||||
animation?: "fade" | "none" | "slide" | undefined;
|
||||
visible: boolean;
|
||||
onRequestClose?: () => void;
|
||||
onShow?: () => void;
|
||||
premium?: boolean;
|
||||
statusBarTranslucent?: boolean;
|
||||
transparent?: boolean;
|
||||
centered?: boolean;
|
||||
bottom?: boolean;
|
||||
background?: string | ColorValue;
|
||||
animated?: boolean;
|
||||
bounce?: boolean;
|
||||
closeOnTouch?: boolean;
|
||||
useSafeArea?: boolean;
|
||||
avoidKeyboardResize?: boolean;
|
||||
enableSheetKeyboardHandler?: boolean;
|
||||
}
|
||||
|
||||
const BaseDialog = ({
|
||||
visible,
|
||||
onRequestClose,
|
||||
@@ -46,14 +66,14 @@ const BaseDialog = ({
|
||||
transparent,
|
||||
centered = true,
|
||||
bottom = false,
|
||||
background = null,
|
||||
animated = true,
|
||||
bounce = true,
|
||||
closeOnTouch = true,
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false,
|
||||
enableSheetKeyboardHandler = false
|
||||
}) => {
|
||||
enableSheetKeyboardHandler,
|
||||
background
|
||||
}: BaseDialogProps) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const appState = useAppState();
|
||||
const lockEvents = useRef(false);
|
||||
@@ -148,7 +168,7 @@ const BaseDialog = ({
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={closeOnTouch ? onRequestClose : null}
|
||||
onPress={closeOnTouch ? onRequestClose : undefined}
|
||||
style={styles.overlayButton}
|
||||
/>
|
||||
{premium}
|
||||
@@ -25,7 +25,7 @@ type DialogInfo = {
|
||||
paragraph?: string;
|
||||
positiveText?: string;
|
||||
negativeText?: string;
|
||||
positivePress?: (value: unknown) => void;
|
||||
positivePress?: (value: any) => void;
|
||||
onClose?: () => void;
|
||||
positiveType?:
|
||||
| "transparent"
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -63,7 +63,7 @@ export class AddTopicDialog extends React.Component {
|
||||
addNewTopic = async () => {
|
||||
try {
|
||||
if (!this.title || this.title?.trim() === "") {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Topic title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
@@ -199,7 +199,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
close = () => {
|
||||
if (this.state.loading) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: this.state.title,
|
||||
message: "Please wait and do not close the app.",
|
||||
type: "success",
|
||||
@@ -236,7 +236,7 @@ export class VaultDialog extends Component {
|
||||
if (this.state.loading) return;
|
||||
|
||||
if (!this.password) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Password not entered",
|
||||
message: "Enter a password for the vault and try again.",
|
||||
type: "error",
|
||||
@@ -245,7 +245,7 @@ export class VaultDialog extends Component {
|
||||
return;
|
||||
}
|
||||
if (this.password && this.password.length < 3) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Password too short",
|
||||
message: "Password must be longer than 3 characters.",
|
||||
type: "error",
|
||||
@@ -257,7 +257,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
if (!this.state.novault) {
|
||||
if (this.password !== this.confirmPassword) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Passwords do not match",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -283,7 +283,7 @@ export class VaultDialog extends Component {
|
||||
if (this.state.biometricUnlock) {
|
||||
this._enrollFingerprint(this.newPassword);
|
||||
}
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Vault password updated successfully",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -295,7 +295,7 @@ export class VaultDialog extends Component {
|
||||
loading: false
|
||||
});
|
||||
if (e.message === db.vault.ERRORS.wrongPassword) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
@@ -305,7 +305,7 @@ export class VaultDialog extends Component {
|
||||
});
|
||||
} else if (this.state.locked) {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
@@ -355,7 +355,7 @@ export class VaultDialog extends Component {
|
||||
});
|
||||
this.close();
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Account password incorrect",
|
||||
message: "Please enter correct password for your account.",
|
||||
type: "error",
|
||||
@@ -382,7 +382,7 @@ export class VaultDialog extends Component {
|
||||
this.close();
|
||||
eSendEvent("vaultUpdated");
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Vault password incorrect",
|
||||
message: "Please enter correct password to clear vault.",
|
||||
type: "error",
|
||||
@@ -396,7 +396,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
async _lockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -408,7 +408,7 @@ export class VaultDialog extends Component {
|
||||
eSendEvent(eClearEditor);
|
||||
}
|
||||
this.close();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
message: "Note locked successfully",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -421,7 +421,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
async _unlockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
@@ -479,7 +479,7 @@ export class VaultDialog extends Component {
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
@@ -488,7 +488,7 @@ export class VaultDialog extends Component {
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this.close();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
@@ -517,7 +517,7 @@ export class VaultDialog extends Component {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note added to vault",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -525,7 +525,7 @@ export class VaultDialog extends Component {
|
||||
this.close();
|
||||
} else {
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Vault created successfully",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -538,7 +538,7 @@ export class VaultDialog extends Component {
|
||||
db.vault
|
||||
.remove(this.state.note.id, this.password)
|
||||
.then(() => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note permanently unlocked.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -553,7 +553,9 @@ export class VaultDialog extends Component {
|
||||
_openInEditor(note) {
|
||||
this.close();
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
eSendEvent(eOnLoadNote, note);
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
@@ -562,7 +564,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
async _copyNote(note) {
|
||||
Clipboard.setString(await convertNoteToText(note));
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note copied",
|
||||
type: "success",
|
||||
message: "Note has been copied to clipboard!",
|
||||
@@ -594,7 +596,7 @@ export class VaultDialog extends Component {
|
||||
visible: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -609,13 +611,13 @@ export class VaultDialog extends Component {
|
||||
try {
|
||||
await BiometricService.resetCredentials();
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Biometric unlocking disabled!",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Failed to disable Biometric unlocking.",
|
||||
message: e.message,
|
||||
type: "success",
|
||||
|
||||
@@ -39,10 +39,7 @@ export const RightMenus = () => {
|
||||
const buttons = useNavigationStore((state) => state.headerRightButtons);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
|
||||
const buttonAction = useNavigationStore((state) => state.buttonAction);
|
||||
const menuRef = useRef<{
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
}>();
|
||||
const menuRef = useRef<Menu>(null);
|
||||
|
||||
return (
|
||||
<View style={styles.rightBtnContainer}>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import Notebook from "../../screens/notebook";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
@@ -32,7 +32,7 @@ import { SIZE } from "../../utils/size";
|
||||
import Tag from "../ui/tag";
|
||||
import Heading from "../ui/typography/heading";
|
||||
|
||||
const titleState: { [name: string]: boolean } = {};
|
||||
const titleState: { [id: string]: boolean } = {};
|
||||
|
||||
export const Title = () => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -76,8 +76,8 @@ export const Title = () => {
|
||||
useEffect(() => {
|
||||
if (currentScreen.name === "Notebook") {
|
||||
const value =
|
||||
typeof titleState[currentScreen.id as string] === "boolean"
|
||||
? titleState[currentScreen.id as string]
|
||||
typeof titleState[currentScreen.id] === "boolean"
|
||||
? titleState[currentScreen.id]
|
||||
: true;
|
||||
setHide(value);
|
||||
} else {
|
||||
@@ -94,7 +94,9 @@ export const Title = () => {
|
||||
|
||||
function navigateToNotebook() {
|
||||
if (!isTopic) return;
|
||||
Notebook.navigate(notebook, true);
|
||||
if (notebook) {
|
||||
NotebookScreen.navigate(notebook, true);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -32,12 +32,12 @@ import {
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
const ImagePreview = () => {
|
||||
const { colors } = useThemeColors("dialog");
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState("");
|
||||
const [image, setImage] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,7 +48,7 @@ const ImagePreview = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = async (image) => {
|
||||
const open = async (image: ImageAttributes) => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
@@ -63,6 +63,7 @@ const ImagePreview = () => {
|
||||
});
|
||||
}
|
||||
if (!hash) return;
|
||||
//@ts-ignore // FIX ME
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
@@ -74,7 +75,7 @@ const ImagePreview = () => {
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setImage(null);
|
||||
setImage(undefined);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
@@ -88,7 +89,7 @@ const ImagePreview = () => {
|
||||
backgroundColor: "black"
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
{loading || !image ? (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -22,7 +22,7 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
import { db } from "../../../common/database";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -50,7 +50,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
type: "notebook"
|
||||
}
|
||||
});
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Shortcut created",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import Notebook from "../../../screens/notebook";
|
||||
import NotebookScreen from "../../../screens/notebook";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||
@@ -83,14 +83,8 @@ function getNotebook(item) {
|
||||
}
|
||||
|
||||
function getTags(item) {
|
||||
const noteTags = item.tags?.slice(0, 3) || [];
|
||||
const tags = [];
|
||||
for (const tagName of noteTags) {
|
||||
const tag = db.tags.tag(tagName);
|
||||
if (!tag) continue;
|
||||
tags.push(tag);
|
||||
}
|
||||
return tags;
|
||||
const noteTags = db.relations.to(item, "tag").resolved();
|
||||
return noteTags;
|
||||
}
|
||||
|
||||
const NoteItem = ({
|
||||
@@ -162,7 +156,7 @@ const NoteItem = ({
|
||||
if (item.type === "topic") {
|
||||
TopicNotes.navigate(item, true);
|
||||
} else {
|
||||
Notebook.navigate(item);
|
||||
NotebookScreen.navigate(item);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -323,7 +317,7 @@ const NoteItem = ({
|
||||
? tags.map((item) =>
|
||||
item.id ? (
|
||||
<Button
|
||||
title={"#" + item.alias}
|
||||
title={"#" + item.title}
|
||||
key={item.id}
|
||||
height={23}
|
||||
type="gray"
|
||||
|
||||
@@ -93,7 +93,9 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
|
||||
});
|
||||
} else {
|
||||
useEditorStore.getState().setReadonly(_note?.readonly);
|
||||
eSendEvent(eOnLoadNote, _note);
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: _note
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { NotebookItem } from ".";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
@@ -67,7 +67,7 @@ export const openNotebookTopic = (item) => {
|
||||
await db.trash.restore(item.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Restore successful",
|
||||
type: "success"
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export const openNotebookTopic = (item) => {
|
||||
await db.trash.delete(item.id);
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
|
||||
@@ -66,7 +66,7 @@ const TagItem = React.memo(
|
||||
>
|
||||
#
|
||||
</Heading>
|
||||
{item.alias}
|
||||
{item.title}
|
||||
</Heading>
|
||||
<Paragraph
|
||||
color={colors.secondary.paragraph}
|
||||
|
||||
@@ -91,7 +91,7 @@ const MergeConflicts = () => {
|
||||
if (editorController.current?.note?.id === note.id) {
|
||||
// reload the note in editor
|
||||
eSendEvent(eOnLoadNote, {
|
||||
...editorController.current?.note,
|
||||
item: editorController.current?.note,
|
||||
forced: true
|
||||
});
|
||||
}
|
||||
@@ -324,10 +324,12 @@ const MergeConflicts = () => {
|
||||
if (!note) return;
|
||||
await sleep(300);
|
||||
eSendEvent(eOnLoadNote + ":conflictPrimary", {
|
||||
...note,
|
||||
content: {
|
||||
...content.current,
|
||||
isPreview: true
|
||||
item: {
|
||||
...note,
|
||||
content: {
|
||||
...content.current,
|
||||
isPreview: true
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
@@ -359,8 +361,10 @@ const MergeConflicts = () => {
|
||||
if (!note) return;
|
||||
await sleep(300);
|
||||
eSendEvent(eOnLoadNote + ":conflictSecondary", {
|
||||
...note,
|
||||
content: { ...content.current.conflicted, isPreview: true }
|
||||
item: {
|
||||
...note,
|
||||
content: { ...content.current.conflicted, isPreview: true }
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import Editor from "../../screens/editor";
|
||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useEditorStore } from "../../stores/use-editor-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
@@ -44,7 +44,7 @@ export default function NotePreview({ session, content, note }) {
|
||||
await db.trash.restore(note.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Restore successful",
|
||||
type: "success"
|
||||
});
|
||||
@@ -55,7 +55,7 @@ export default function NotePreview({ session, content, note }) {
|
||||
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
|
||||
if (editorController.current?.note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
...editorController.current?.note,
|
||||
item: editorController.current?.note,
|
||||
forced: true
|
||||
});
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export default function NotePreview({ session, content, note }) {
|
||||
eSendEvent(eCloseSheet);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note restored successfully",
|
||||
type: "success"
|
||||
});
|
||||
@@ -81,7 +81,7 @@ export default function NotePreview({ session, content, note }) {
|
||||
await db.trash.delete(note.id);
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
@@ -115,10 +115,12 @@ export default function NotePreview({ session, content, note }) {
|
||||
onLoad={async () => {
|
||||
const _note = note || db.notes.note(session?.noteId)?.data;
|
||||
eSendEvent(eOnLoadNote + editorId, {
|
||||
..._note,
|
||||
content: {
|
||||
...content,
|
||||
isPreview: true
|
||||
item: {
|
||||
..._note,
|
||||
content: {
|
||||
...content,
|
||||
isPreview: true
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { usePricing } from "../../hooks/use-pricing";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -517,7 +517,7 @@ export const PricingPlans = ({
|
||||
try {
|
||||
if (!(await getPromo(value as string)))
|
||||
throw new Error("Error applying promo code");
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Discount applied!",
|
||||
type: "success",
|
||||
context: "local"
|
||||
@@ -525,7 +525,7 @@ export const PricingPlans = ({
|
||||
setBuying(false);
|
||||
} catch (e) {
|
||||
setBuying(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Promo code invalid or expired",
|
||||
message: (e as Error).message,
|
||||
type: "error",
|
||||
|
||||
@@ -17,7 +17,10 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { DefaultColors } from "@notesnook/core/dist/collections/colors";
|
||||
import { Note } from "@notesnook/core/dist/types";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
@@ -25,45 +28,77 @@ import { db } from "../../common/database";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { ColorValues } from "../../utils/colors";
|
||||
import { refreshNotesPage } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const ColorTags = ({ item }) => {
|
||||
export const ColorTags = ({ item }: { item: Note }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [note, setNote] = useState(item);
|
||||
const setColorNotes = useMenuStore((state) => state.setColorNotes);
|
||||
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
|
||||
const changeColor = async (color) => {
|
||||
if (note.color === color.name) {
|
||||
await db.notes.note(note.id).uncolor();
|
||||
} else {
|
||||
await db.notes.note(note.id).color(color.name);
|
||||
const updater = useRelationStore((state) => state.updater);
|
||||
|
||||
const getColorInfo = (colorCode: string) => {
|
||||
const dbColor = db.colors.all.find((v) => v.colorCode === colorCode);
|
||||
let isLinked = false;
|
||||
|
||||
if (dbColor) {
|
||||
const note = db.relations
|
||||
.from(dbColor, "note")
|
||||
.find((relation) => relation.to.id === item.id);
|
||||
|
||||
if (note) {
|
||||
isLinked = true;
|
||||
}
|
||||
}
|
||||
let _note = db.notes.note(note.id).data;
|
||||
setNote({ ..._note });
|
||||
|
||||
return {
|
||||
linked: isLinked,
|
||||
item: dbColor
|
||||
};
|
||||
};
|
||||
|
||||
const changeColor = async (color: string) => {
|
||||
const colorInfo = getColorInfo(DefaultColors[color]);
|
||||
|
||||
if (colorInfo.item) {
|
||||
if (colorInfo.linked) {
|
||||
await db.relations.unlink(colorInfo.item, item);
|
||||
} else {
|
||||
await db.relations.add(colorInfo.item, item);
|
||||
}
|
||||
} else {
|
||||
const colorId = await db.colors.add({
|
||||
title: color,
|
||||
colorCode: DefaultColors[color]
|
||||
});
|
||||
|
||||
const dbColor = db.colors.color(colorId);
|
||||
if (dbColor) {
|
||||
await db.relations.add(dbColor, item);
|
||||
}
|
||||
}
|
||||
|
||||
useRelationStore.getState().update();
|
||||
setColorNotes();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(refreshNotesPage);
|
||||
};
|
||||
|
||||
const _renderColor = (c) => {
|
||||
const color = {
|
||||
name: c,
|
||||
value: ColorValues[c?.toLowerCase()]
|
||||
};
|
||||
const _renderColor = (name: keyof typeof DefaultColors) => {
|
||||
const color = DefaultColors[name];
|
||||
const colorInfo = getColorInfo(color);
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
type="accent"
|
||||
accentColor={colors.static[color.name?.toLowerCase()]}
|
||||
accentColor={color}
|
||||
accentText={colors.static.white}
|
||||
testID={notesnook.ids.dialogs.actionsheet.color(c)}
|
||||
key={color.value}
|
||||
onPress={() => changeColor(color)}
|
||||
testID={notesnook.ids.dialogs.actionsheet.color(name)}
|
||||
key={color}
|
||||
onPress={() => changeColor(name)}
|
||||
customStyle={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
@@ -73,7 +108,7 @@ export const ColorTags = ({ item }) => {
|
||||
marginRight: isTablet ? 10 : undefined
|
||||
}}
|
||||
>
|
||||
{note.color?.toLowerCase() === color.name ? (
|
||||
{colorInfo.linked ? (
|
||||
<Icon testID="icon-check" name="check" color="white" size={SIZE.lg} />
|
||||
) : null}
|
||||
</PressableButton>
|
||||
@@ -92,7 +127,7 @@ export const ColorTags = ({ item }) => {
|
||||
justifyContent: isTablet ? "center" : "space-between"
|
||||
}}
|
||||
>
|
||||
{Object.keys(ColorValues).map(_renderColor)}
|
||||
{Object.keys(DefaultColors).map(_renderColor)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -52,7 +52,6 @@ const Line = ({ top = 6, bottom = 6 }) => {
|
||||
|
||||
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const alias = item.alias || item.title;
|
||||
const isColor = !!ColorValues[item.title];
|
||||
if (!item || !item.id) {
|
||||
return (
|
||||
@@ -90,7 +89,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
{alias}
|
||||
{item.title}
|
||||
</Heading>
|
||||
|
||||
{item.type === "note" ? (
|
||||
|
||||
@@ -177,17 +177,37 @@ export const Items = ({ item, buttons, close }) => {
|
||||
"export",
|
||||
"lock-unlock"
|
||||
];
|
||||
|
||||
if (!shouldShrink) {
|
||||
topBarItemsList.push("publish");
|
||||
}
|
||||
|
||||
const topBarItems = data.filter(
|
||||
(item) => topBarItemsList.indexOf(item.id) > -1
|
||||
);
|
||||
const topBarItems = data
|
||||
.filter((item) => topBarItemsList.indexOf(item.id) > -1)
|
||||
.sort((a, b) =>
|
||||
topBarItemsList.indexOf(a.id) > topBarItemsList.indexOf(b.id) ? 1 : -1
|
||||
);
|
||||
|
||||
const bottomGridItems = data.filter(
|
||||
(item) => topBarItemsList.indexOf(item.id) === -1
|
||||
);
|
||||
const bottomBarItemsList = [
|
||||
"notebooks",
|
||||
"add-reminder",
|
||||
"pin-to-notifications",
|
||||
"duplicate",
|
||||
"read-only",
|
||||
"local-only",
|
||||
"history",
|
||||
"reminders",
|
||||
"attachments",
|
||||
"trash"
|
||||
];
|
||||
|
||||
const bottomGridItems = data
|
||||
.filter((item) => bottomBarItemsList.indexOf(item.id) > -1)
|
||||
.sort((a, b) =>
|
||||
bottomBarItemsList.indexOf(a.id) > bottomBarItemsList.indexOf(b.id)
|
||||
? 1
|
||||
: -1
|
||||
);
|
||||
|
||||
const topBarItemWidth =
|
||||
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
|
||||
|
||||
@@ -21,12 +21,12 @@ import React from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import Notebook from "../../screens/notebook";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useNotebookStore } from "../../stores/use-notebook-store";
|
||||
@@ -75,7 +75,7 @@ export default function Notebooks({ note, close, full }) {
|
||||
const navigateNotebook = (id) => {
|
||||
let item = db.notebooks.notebook(id)?.data;
|
||||
if (!item) return;
|
||||
Notebook.navigate(item, true);
|
||||
NotebookScreen.navigate(item, true);
|
||||
};
|
||||
|
||||
const navigateTopic = (id, notebookId) => {
|
||||
@@ -153,7 +153,7 @@ export default function Notebooks({ note, close, full }) {
|
||||
);
|
||||
useNotebookStore.getState().setNotebooks();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note removed from topic",
|
||||
context: "local",
|
||||
type: "success"
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, { useCallback, useEffect } from "react";
|
||||
import { BackHandler, Platform, View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
@@ -80,7 +80,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
|
||||
clearSelection();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Restore successful",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ import { FlatList } from "react-native-actions-sheet";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import { ToastEvent, presentSheet } from "../../../services/event-manager";
|
||||
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
@@ -129,7 +129,7 @@ export class AddNotebookSheet extends React.Component {
|
||||
let { topics, notebook } = this.state;
|
||||
|
||||
if (!this.title || this.title?.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Notebook title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
|
||||
@@ -24,7 +24,7 @@ import { db } from "../../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
@@ -59,7 +59,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
|
||||
const onAddNotebook = async (title) => {
|
||||
if (!title || title.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Notebook title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -94,7 +94,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
const onAddTopic = useCallback(
|
||||
async (value, item) => {
|
||||
if (!value || value.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Topic title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
PresentSheetOptions,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
@@ -87,7 +87,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
);
|
||||
eSendEvent(eUserLoggedIn);
|
||||
close?.();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: `Email changed`,
|
||||
message: `Your account email has been updated to ${emailChangeData.current.email}`,
|
||||
type: "success",
|
||||
@@ -96,7 +96,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.error(e as Error);
|
||||
ToastManager.error(e as Error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import Share from "react-native-share";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import { presentSheet, ToastManager } from "../../../services/event-manager";
|
||||
import Exporter from "../../../services/exporter";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -286,7 +286,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
showAppsSuggestions: true
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Cannot open",
|
||||
message: `No application found to open ${result.name} file.`,
|
||||
type: "success",
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
export const ShareComponent = ({ uri, name, padding }) => {
|
||||
@@ -40,7 +40,7 @@ export const ShareComponent = ({ uri, name, padding }) => {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
}).catch(() => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Cannot open",
|
||||
message: `No application found to open ${name} file.`,
|
||||
type: "success",
|
||||
|
||||
@@ -22,7 +22,7 @@ import React, { useRef, useState } from "react";
|
||||
import { Linking, Platform, Text, TextInput, View } from "react-native";
|
||||
import { getVersion } from "react-native-device-info";
|
||||
import { db } from "../../../common/database";
|
||||
import { eSendEvent, ToastEvent } from "../../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../../services/event-manager";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
@@ -100,7 +100,7 @@ Logged in: ${user ? "yes" : "no"}`,
|
||||
positiveText: "Copy link",
|
||||
positivePress: () => {
|
||||
Clipboard.setString(issue_url);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Issue url copied!",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -110,8 +110,8 @@ Logged in: ${user ? "yes" : "no"}`,
|
||||
});
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: "An error occurred",
|
||||
ToastManager.show({
|
||||
heading: "An error occured",
|
||||
message: e.message,
|
||||
type: "error"
|
||||
});
|
||||
|
||||
@@ -17,68 +17,89 @@ 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, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { Tags } from "@notesnook/core/dist/collections/tags";
|
||||
import { GroupedItems, Note, Tag } from "@notesnook/core/dist/types";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, {
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import { ToastEvent, presentSheet } from "../../../services/event-manager";
|
||||
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
const ManageTagsSheet = (props) => {
|
||||
|
||||
function ungroup(items: GroupedItems<Tag>) {
|
||||
return items.filter((item) => item.type !== "header") as Tag[];
|
||||
}
|
||||
|
||||
const ManageTagsSheet = (props: {
|
||||
notes?: Note[];
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [notes, setNotes] = useState(props.notes || []);
|
||||
const allTags = useTagStore((state) => state.tags);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [query, setQuery] = useState(null);
|
||||
const inputRef = useRef();
|
||||
const notes = useMemo(() => props.notes || [], [props.notes]);
|
||||
const allTags = useTagStore((state) => ungroup(state.tags));
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [query, setQuery] = useState<string>();
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const [focus, setFocus] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
sortTags();
|
||||
}, [allTags, notes, query, sortTags]);
|
||||
|
||||
const sortTags = useCallback(() => {
|
||||
let _tags = [...allTags];
|
||||
_tags = _tags.filter((t) => t.type === "tag");
|
||||
let _tags = db.tags.all;
|
||||
|
||||
_tags = _tags.sort((a, b) => a.title.localeCompare(b.title));
|
||||
if (query) {
|
||||
_tags = db.lookup.tags(_tags, query);
|
||||
_tags = db.lookup.tags(_tags, query) as Tag[];
|
||||
}
|
||||
const tagsMerged = [...notes.map((note) => note.tags || []).flat()];
|
||||
let tagsMerged = notes
|
||||
.map((note) => db.relations.to(note, "tag").resolved())
|
||||
.flat();
|
||||
// Get unique tags and remove duplicates
|
||||
tagsMerged = [
|
||||
...new Map(tagsMerged.map((item) => [item.id, item])).values()
|
||||
];
|
||||
|
||||
if (!tagsMerged || !tagsMerged.length) {
|
||||
setTags(_tags);
|
||||
return;
|
||||
}
|
||||
|
||||
let noteTags = [];
|
||||
for (let tag of tagsMerged) {
|
||||
let index = _tags.findIndex((t) => t.title === tag);
|
||||
for (const tag of tagsMerged) {
|
||||
const index = _tags.findIndex((t) => t.id === tag.id);
|
||||
if (index !== -1) {
|
||||
noteTags.push(_tags[index]);
|
||||
_tags.splice(index, 1);
|
||||
}
|
||||
}
|
||||
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
|
||||
let combinedTags = [...noteTags, ..._tags];
|
||||
setTags(combinedTags);
|
||||
}, [allTags, notes, query]);
|
||||
const combinedTags = [...noteTags, ..._tags];
|
||||
|
||||
useEffect(() => {
|
||||
useTagStore.getState().setTags();
|
||||
}, []);
|
||||
setTags(combinedTags);
|
||||
}, [notes, query]);
|
||||
|
||||
// useEffect(() => {
|
||||
// sortTags();
|
||||
// }, [allTags.length]);
|
||||
|
||||
const onSubmit = async () => {
|
||||
let _query = query;
|
||||
if (!_query || _query === "" || _query.trimStart().length == 0) {
|
||||
ToastEvent.show({
|
||||
if (!query || query === "" || query.trimStart().length == 0) {
|
||||
ToastManager.show({
|
||||
heading: "Tag field is empty",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -86,29 +107,36 @@ const ManageTagsSheet = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let tag = _query;
|
||||
setNotes(
|
||||
notes.map((note) => ({
|
||||
...note,
|
||||
tags: note.tags ? [...note.tags, tag] : [tag]
|
||||
}))
|
||||
);
|
||||
const tag = query;
|
||||
setQuery(undefined);
|
||||
|
||||
setQuery(null);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: ""
|
||||
});
|
||||
try {
|
||||
for (let note of notes) {
|
||||
await db.notes.note(note.id).tag(tag);
|
||||
const exists = db.tags.all.filter((t: Tag) => t.title === tag);
|
||||
const id = exists.length
|
||||
? exists[0]?.id
|
||||
: await db.tags.add({
|
||||
title: tag
|
||||
});
|
||||
|
||||
const createdTag = db.tags.tag(id);
|
||||
if (createdTag) {
|
||||
for (const note of notes) {
|
||||
await db.relations.add(createdTag, note);
|
||||
}
|
||||
}
|
||||
useRelationStore.getState().update();
|
||||
useTagStore.getState().setTags();
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
setTimeout(() => {
|
||||
sortTags();
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Cannot add tag",
|
||||
type: "error",
|
||||
message: e.message,
|
||||
message: (e as Error).message,
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
@@ -129,13 +157,17 @@ const ManageTagsSheet = (props) => {
|
||||
button={{
|
||||
icon: "magnify",
|
||||
color: colors.primary.accent,
|
||||
size: SIZE.lg
|
||||
size: SIZE.lg,
|
||||
onPress: () => {}
|
||||
}}
|
||||
testID="tag-input"
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(v) => {
|
||||
setQuery(db.tags.sanitize(v));
|
||||
setQuery(Tags.sanitize(v));
|
||||
setTimeout(() => {
|
||||
sortTags();
|
||||
});
|
||||
}}
|
||||
onFocusInput={() => {
|
||||
setFocus(true);
|
||||
@@ -143,7 +175,9 @@ const ManageTagsSheet = (props) => {
|
||||
onBlurInput={() => {
|
||||
setFocus(false);
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
onSubmit={() => {
|
||||
onSubmit();
|
||||
}}
|
||||
placeholder="Search or add a tag"
|
||||
/>
|
||||
|
||||
@@ -193,19 +227,14 @@ const ManageTagsSheet = (props) => {
|
||||
) : null}
|
||||
|
||||
{tags.map((item) => (
|
||||
<TagItem
|
||||
key={item.title}
|
||||
tag={item}
|
||||
notes={notes}
|
||||
setNotes={setNotes}
|
||||
/>
|
||||
<TagItem key={item.id} tag={item} notes={notes} />
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ManageTagsSheet.present = (notes) => {
|
||||
ManageTagsSheet.present = (notes?: Note[]) => {
|
||||
presentSheet({
|
||||
component: (ref) => {
|
||||
return <ManageTagsSheet actionSheetRef={ref} notes={notes} />;
|
||||
@@ -215,30 +244,34 @@ ManageTagsSheet.present = (notes) => {
|
||||
|
||||
export default ManageTagsSheet;
|
||||
|
||||
const TagItem = ({ tag, notes, setNotes }) => {
|
||||
const TagItem = ({ tag, notes }: { tag: Tag; notes: Note[] }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const someNotesTagged = notes.some(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
const allNotesTagged = notes.every(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
const update = useRelationStore((state) => state.updater);
|
||||
|
||||
const someNotesTagged = notes.some((note) => {
|
||||
const relations = db.relations.from(tag, "note");
|
||||
return relations.findIndex((relation) => relation.to.id === note.id) > -1;
|
||||
});
|
||||
|
||||
const allNotesTagged = notes.every((note) => {
|
||||
const relations = db.relations.from(tag, "note");
|
||||
return relations.findIndex((relation) => relation.to.id === note.id) > -1;
|
||||
});
|
||||
|
||||
const onPress = async () => {
|
||||
for (let note of notes) {
|
||||
for (const note of notes) {
|
||||
try {
|
||||
if (someNotesTagged) {
|
||||
await db.notes
|
||||
.note(note.id)
|
||||
.untag(note.tags[note.tags.indexOf(tag.title)]);
|
||||
await db.relations.unlink(tag, note);
|
||||
} else {
|
||||
await db.notes.note(note.id).tag(tag.title);
|
||||
await db.relations.add(tag, note);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
useRelationStore.getState().update();
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 1);
|
||||
@@ -281,7 +314,7 @@ const TagItem = ({ tag, notes, setNotes }) => {
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
<Paragraph size={SIZE.sm}>{"#" + tag.alias}</Paragraph>
|
||||
<Paragraph size={SIZE.sm}>{"#" + tag.title}</Paragraph>
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
@@ -26,7 +26,7 @@ import BackupService from "../../../services/backup";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -94,7 +94,7 @@ export default function Migrate() {
|
||||
await sleep(1000);
|
||||
const backupSaved = await BackupService.run(false, "local");
|
||||
if (!backupSaved) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Migration failed",
|
||||
message: "You must download a backup of your data before migrating.",
|
||||
context: "local"
|
||||
|
||||
@@ -17,7 +17,8 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NotebookType, NoteType, TopicType } from "app/utils/types";
|
||||
import { Note, Notebook, Topic } from "@notesnook/core/dist/types";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useState } from "react";
|
||||
import { Platform, useWindowDimensions, View } from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
@@ -26,11 +27,10 @@ import { db } from "../../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Dialog } from "../../dialog";
|
||||
@@ -43,27 +43,19 @@ import Seperator from "../../ui/seperator";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
type CommonItemType = {
|
||||
id: string;
|
||||
title: string;
|
||||
headline?: string;
|
||||
type: string;
|
||||
notes?: string[];
|
||||
};
|
||||
|
||||
export const MoveNotes = ({
|
||||
notebook,
|
||||
selectedTopic,
|
||||
fwdRef
|
||||
}: {
|
||||
notebook: NotebookType;
|
||||
selectedTopic?: TopicType;
|
||||
notebook: Notebook;
|
||||
selectedTopic?: Topic;
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [currentNotebook, setCurrentNotebook] = useState(notebook);
|
||||
const { height } = useWindowDimensions();
|
||||
let notes = db.notes?.all as NoteType[];
|
||||
let notes = db.notes?.all;
|
||||
|
||||
const [selectedNoteIds, setSelectedNoteIds] = useState<string[]>([]);
|
||||
const [topic, setTopic] = useState(selectedTopic);
|
||||
@@ -110,30 +102,34 @@ export const MoveNotes = ({
|
||||
|
||||
const addNewTopic = async (value: string) => {
|
||||
if (!value || value.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Topic title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
await db.notebooks?.notebook(currentNotebook.id).topics.add(value);
|
||||
setCurrentNotebook(
|
||||
db.notebooks?.notebook(currentNotebook.id).data as NotebookType
|
||||
);
|
||||
await db.notebooks?.notebook(currentNotebook.id)?.topics.add({
|
||||
title: value
|
||||
});
|
||||
|
||||
const notebook = db.notebooks?.notebook(currentNotebook.id);
|
||||
if (notebook) {
|
||||
setCurrentNotebook(notebook.data);
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate();
|
||||
return true;
|
||||
};
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ item }: { item: CommonItemType }) => {
|
||||
({ item }: { item: Topic | Note }) => {
|
||||
return (
|
||||
<PressableButton
|
||||
testID="listitem.select"
|
||||
onPress={() => {
|
||||
if (item.type == "topic") {
|
||||
setTopic(topic || (item as TopicType));
|
||||
setTopic(topic || item);
|
||||
} else {
|
||||
select(item.id);
|
||||
}
|
||||
@@ -337,7 +333,7 @@ export const MoveNotes = ({
|
||||
);
|
||||
};
|
||||
|
||||
MoveNotes.present = (notebook: NotebookType, topic: TopicType) => {
|
||||
MoveNotes.present = (notebook: Notebook, topic: Topic) => {
|
||||
presentSheet({
|
||||
component: (ref: RefObject<ActionSheetRef>) => (
|
||||
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
|
||||
|
||||
@@ -22,7 +22,7 @@ import React, { useRef, useState } from "react";
|
||||
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import { presentSheet, ToastManager } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useAttachmentStore } from "../../../stores/use-attachment-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
@@ -48,8 +48,8 @@ const PublishNoteSheet = ({ note: item }) => {
|
||||
const [note, setNote] = useState(item);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const publishUrl =
|
||||
note && `https://monogr.ph/${db?.monographs.monograph(note?.id)}`;
|
||||
const isPublished = note && db?.monographs.isPublished(note?.id);
|
||||
note && `https://monogr.ph/${db.monographs.monograph(note?.id)}`;
|
||||
const isPublished = note && db.monographs.isPublished(note?.id);
|
||||
const pwdInput = useRef();
|
||||
const passwordValue = useRef();
|
||||
|
||||
@@ -70,7 +70,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
||||
}
|
||||
requestInAppReview();
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Could not publish note",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
@@ -96,7 +96,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Could not unpublish note",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
@@ -193,7 +193,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
Clipboard.setString(publishUrl);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Note publish url copied",
|
||||
type: "success",
|
||||
context: "local"
|
||||
|
||||
@@ -23,11 +23,10 @@ import { Platform, View } from "react-native";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Share from "react-native-share";
|
||||
//import { LOGO_BASE64 } from '../../../assets/images/assets';
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import { clearMessage } from "../../../services/message";
|
||||
import SettingsService from "../../../services/settings";
|
||||
@@ -75,7 +74,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
|
||||
close = () => {
|
||||
if (this.tapCount === 0) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Did you save recovery key?",
|
||||
message: "Tap one more time to confirm.",
|
||||
type: "success",
|
||||
@@ -129,7 +128,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(path + fileName, data, "base64");
|
||||
}
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery key QR-Code saved",
|
||||
message:
|
||||
"QR-Code image has been saved to Gallery at " + path + fileName,
|
||||
@@ -164,7 +163,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
path = path + fileName;
|
||||
}
|
||||
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery key text file saved",
|
||||
message: "Recovery key saved in text file.",
|
||||
type: "success",
|
||||
@@ -284,7 +283,7 @@ class RecoveryKeySheet extends React.Component {
|
||||
<Button
|
||||
onPress={() => {
|
||||
Clipboard.setString(this.state.key);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery key copied!",
|
||||
type: "success",
|
||||
context: "local"
|
||||
|
||||
@@ -35,6 +35,7 @@ import SheetProvider from "../../sheet-provider";
|
||||
import { Button } from "../../ui/button";
|
||||
import { PressableButtonProps } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { ItemReference, ItemType } from "@notesnook/core/dist/types";
|
||||
|
||||
type RelationsListProps = {
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
@@ -73,10 +74,10 @@ export const RelationsList = ({
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
const items =
|
||||
(db.relations?.[relationType]?.(
|
||||
{ id: item?.id, type: item?.type },
|
||||
referenceType
|
||||
) as any) || [];
|
||||
db.relations?.[relationType]?.(
|
||||
{ id: item?.id, type: item?.type } as ItemReference,
|
||||
referenceType as ItemType
|
||||
) || [];
|
||||
|
||||
const hasNoRelations = !items || items.length === 0;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
PresentSheetOptions,
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -39,18 +39,18 @@ import Notifications, { Reminder } from "../../../services/notifications";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { ItemReference } from "@notesnook/core/dist/types";
|
||||
|
||||
type ReminderSheetProps = {
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: (ctx?: string) => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
reminder?: Reminder;
|
||||
reference?: { id: string; type: string };
|
||||
reference?: ItemReference;
|
||||
};
|
||||
|
||||
const ReminderModes =
|
||||
@@ -113,9 +113,8 @@ export default function ReminderSheet({
|
||||
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const referencedItem = reference
|
||||
? (db.notes?.note(reference.id)?.data as NoteType)
|
||||
: null;
|
||||
const referencedItem = reference ? db.notes?.note(reference.id)?.data : null;
|
||||
|
||||
const title = useRef<string | undefined>(
|
||||
reminder?.title || referencedItem?.title
|
||||
);
|
||||
@@ -195,19 +194,19 @@ export default function ReminderSheet({
|
||||
date?.getTime() > Date.now() ? undefined : reminder?.snoozeUntil,
|
||||
disabled: false
|
||||
});
|
||||
|
||||
if (!reminderId) return;
|
||||
const _reminder = db.reminders?.reminder(reminderId);
|
||||
|
||||
if (!_reminder) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Failed to add a new reminder",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
if (reference) {
|
||||
if (reference && _reminder) {
|
||||
await db.relations?.add(reference, {
|
||||
id: _reminder?.id as string,
|
||||
type: _reminder?.type as string
|
||||
type: _reminder?.type
|
||||
});
|
||||
}
|
||||
Notifications.scheduleNotification(_reminder as Reminder);
|
||||
@@ -215,7 +214,7 @@ export default function ReminderSheet({
|
||||
useRelationStore.getState().update();
|
||||
close?.();
|
||||
} catch (e) {
|
||||
ToastEvent.error(e as Error, undefined, "local");
|
||||
ToastManager.error(e as Error, undefined, "local");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,7 +613,7 @@ export default function ReminderSheet({
|
||||
|
||||
ReminderSheet.present = (
|
||||
reminder?: Reminder,
|
||||
reference?: { id: string; type: string },
|
||||
reference?: ItemReference,
|
||||
isSheet?: boolean
|
||||
) => {
|
||||
presentSheet({
|
||||
|
||||
@@ -31,7 +31,7 @@ import { db } from "../../../common/database";
|
||||
import storage from "../../../common/database/storage";
|
||||
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
@@ -79,7 +79,7 @@ const RestoreDataSheet = () => {
|
||||
}, [restoring]);
|
||||
|
||||
const showIsWorking = () => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Restoring Backup",
|
||||
message: "Your backup data is being restored. please wait.",
|
||||
type: "error",
|
||||
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
eOpenAddTopicDialog
|
||||
} from "../../../utils/events";
|
||||
import { normalize, SIZE } from "../../../utils/size";
|
||||
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
|
||||
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
@@ -65,6 +64,7 @@ import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { Properties } from "../../properties";
|
||||
import Sort from "../sort";
|
||||
import { GroupedItems, GroupHeader, Topic } from "@notesnook/core/dist/types";
|
||||
|
||||
type ConfigItem = { id: string; type: string };
|
||||
class TopicSheetConfig {
|
||||
@@ -95,12 +95,12 @@ export const TopicsSheet = () => {
|
||||
)?.data
|
||||
: null
|
||||
);
|
||||
const [selection, setSelection] = useState<TopicType[]>([]);
|
||||
const [selection, setSelection] = useState<Topic[]>([]);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const ref = useRef<ActionSheetRef>(null);
|
||||
const isTopic = currentScreen.name === "TopicNotes";
|
||||
const [topics, setTopics] = useState(
|
||||
const [topics, setTopics] = useState<GroupedItems<Topic>>(
|
||||
notebook
|
||||
? qclone(
|
||||
groupArray(notebook.topics, db.settings.getGroupOptions("topics"))
|
||||
@@ -117,11 +117,10 @@ export const TopicsSheet = () => {
|
||||
(data?: NotebookScreenParams) => {
|
||||
if (!canShow) return;
|
||||
if (!data) data = { item: notebook } as NotebookScreenParams;
|
||||
const _notebook = db.notebooks?.notebook(data.item?.id)
|
||||
?.data as NotebookType;
|
||||
const _notebook = db.notebooks?.notebook(data.item?.id)?.data;
|
||||
|
||||
if (_notebook) {
|
||||
setNotebook(_notebook);
|
||||
|
||||
setTopics(
|
||||
qclone(
|
||||
groupArray(_notebook.topics, db.settings.getGroupOptions("topics"))
|
||||
@@ -163,6 +162,7 @@ export const TopicsSheet = () => {
|
||||
paragraph: "You have not added any topics yet.",
|
||||
button: "Add first topic",
|
||||
action: () => {
|
||||
if (!notebook) return;
|
||||
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
|
||||
},
|
||||
loading: "Loading notebook topics"
|
||||
@@ -172,18 +172,18 @@ export const TopicsSheet = () => {
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: TopicType | GroupHeader;
|
||||
item: Topic | GroupHeader;
|
||||
index: number;
|
||||
}) =>
|
||||
(item as GroupHeader).type === "header" ? null : (
|
||||
<TopicItem sheetRef={ref} item={item as TopicType} index={index} />
|
||||
<TopicItem sheetRef={ref} item={item as Topic} index={index} />
|
||||
);
|
||||
|
||||
const selectionContext = {
|
||||
selection: selection,
|
||||
enabled,
|
||||
setEnabled,
|
||||
toggleSelection: (item: TopicType) => {
|
||||
toggleSelection: (item: Topic) => {
|
||||
setSelection((state) => {
|
||||
const selection = [...state];
|
||||
const index = selection.findIndex(
|
||||
@@ -428,7 +428,7 @@ export const TopicsSheet = () => {
|
||||
progressBackgroundColor={colors.primary.background}
|
||||
/>
|
||||
}
|
||||
keyExtractor={(item) => (item as TopicType).id}
|
||||
keyExtractor={(item) => (item as Topic).id}
|
||||
renderItem={renderTopic}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
@@ -451,15 +451,15 @@ export const TopicsSheet = () => {
|
||||
};
|
||||
|
||||
const SelectionContext = createContext<{
|
||||
selection: TopicType[];
|
||||
selection: Topic[];
|
||||
enabled: boolean;
|
||||
setEnabled: (value: boolean) => void;
|
||||
toggleSelection: (item: TopicType) => void;
|
||||
toggleSelection: (item: Topic) => void;
|
||||
}>({
|
||||
selection: [],
|
||||
enabled: false,
|
||||
setEnabled: (_value: boolean) => {},
|
||||
toggleSelection: (_item: TopicType) => {}
|
||||
toggleSelection: (_item: Topic) => {}
|
||||
});
|
||||
const useSelection = () => useContext(SelectionContext);
|
||||
|
||||
@@ -468,7 +468,7 @@ const TopicItem = ({
|
||||
index,
|
||||
sheetRef
|
||||
}: {
|
||||
item: TopicType;
|
||||
item: Topic;
|
||||
index: number;
|
||||
sheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
|
||||
@@ -17,22 +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, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Navigation from "../../services/navigation";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { ColorValues } from "../../utils/colors";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { normalize, SIZE } from "../../utils/size";
|
||||
import { ColoredNotes } from "../../screens/notes/colored";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { SIZE, normalize } from "../../utils/size";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { ColoredNotes } from "../../screens/notes/colored";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const ColorSection = React.memo(
|
||||
function ColorSection() {
|
||||
@@ -47,21 +45,18 @@ export const ColorSection = React.memo(
|
||||
}, [loading, setColorNotes]);
|
||||
|
||||
return colorNotes.map((item, index) => {
|
||||
let alias = db.colors.alias(item.id);
|
||||
return (
|
||||
<ColorItem key={item.id} alias={alias} item={item} index={index} />
|
||||
);
|
||||
return <ColorItem key={item.id} item={item} index={index} />;
|
||||
});
|
||||
},
|
||||
() => true
|
||||
);
|
||||
|
||||
const ColorItem = React.memo(
|
||||
function ColorItem({ item, alias }) {
|
||||
function ColorItem({ item }) {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const setColorNotes = useMenuStore((state) => state.setColorNotes);
|
||||
const [headerTextState, setHeaderTextState] = useState(null);
|
||||
alias = db.colors.alias(item.id) || "";
|
||||
const isFocused = headerTextState?.id === item.id;
|
||||
|
||||
const onHeaderStateChange = useCallback(
|
||||
(state) => {
|
||||
@@ -99,11 +94,14 @@ const ColorItem = React.memo(
|
||||
title: "Rename color",
|
||||
input: true,
|
||||
inputPlaceholder: "Enter name for this color",
|
||||
defaultValue: alias,
|
||||
defaultValue: item.title,
|
||||
paragraph: "You are renaming the color " + item.title,
|
||||
positivePress: async (value) => {
|
||||
if (!value || value.trim().length === 0) return;
|
||||
await db.colors.rename(item.id, value);
|
||||
await db.colors.add({
|
||||
id: item.id,
|
||||
title: value
|
||||
});
|
||||
setColorNotes();
|
||||
},
|
||||
positiveText: "Rename"
|
||||
@@ -112,11 +110,9 @@ const ColorItem = React.memo(
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
customColor={
|
||||
headerTextState?.id === item.id ? "rgba(0,0,0,0.04)" : "transparent"
|
||||
}
|
||||
customColor={isFocused ? "rgba(0,0,0,0.04)" : "transparent"}
|
||||
onLongPress={onLongPress}
|
||||
customSelectedColor={ColorValues[item.title.toLowerCase()]}
|
||||
customSelectedColor={item.colorCode}
|
||||
customAlpha={!isDark ? -0.02 : 0.02}
|
||||
customOpacity={0.12}
|
||||
onPress={() => onPress(item)}
|
||||
@@ -149,20 +145,20 @@ const ColorItem = React.memo(
|
||||
style={{
|
||||
width: SIZE.lg - 2,
|
||||
height: SIZE.lg - 2,
|
||||
backgroundColor: ColorValues[item.title.toLowerCase()],
|
||||
backgroundColor: item.colorCode,
|
||||
borderRadius: 100,
|
||||
justifyContent: "center",
|
||||
marginRight: 10
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{headerTextState?.id === item.id ? (
|
||||
{isFocused ? (
|
||||
<Heading color={colors.selected.heading} size={SIZE.md}>
|
||||
{alias.slice(0, 1).toUpperCase() + alias.slice(1)}
|
||||
{item.title?.slice(0, 1).toUpperCase() + item.title.slice(1)}
|
||||
</Heading>
|
||||
) : (
|
||||
<Paragraph color={colors.primary.paragraph} size={SIZE.md}>
|
||||
{alias.slice(0, 1).toUpperCase() + alias.slice(1)}
|
||||
{item.title?.slice(0, 1).toUpperCase() + item.title.slice(1)}
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
@@ -171,8 +167,8 @@ const ColorItem = React.memo(
|
||||
},
|
||||
(prev, next) => {
|
||||
if (!next.item) return false;
|
||||
if (prev.item?.title !== next.item.title) return false;
|
||||
if (prev.item?.dateModified !== next.item?.dateModified) return false;
|
||||
if (prev.alias !== next.alias) return false;
|
||||
if (prev.item?.id !== next.item?.id) return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import Notebook from "../../screens/notebook";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import { TaggedNotes } from "../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
||||
import Navigation from "../../services/navigation";
|
||||
@@ -54,7 +54,7 @@ export const TagsSection = React.memo(
|
||||
|
||||
const onPress = (item) => {
|
||||
if (item.type === "notebook") {
|
||||
Notebook.navigate(item);
|
||||
NotebookScreen.navigate(item);
|
||||
} else if (item.type === "tag") {
|
||||
TaggedNotes.navigate(item);
|
||||
} else {
|
||||
@@ -65,10 +65,7 @@ export const TagsSection = React.memo(
|
||||
});
|
||||
};
|
||||
const renderItem = ({ item, index }) => {
|
||||
let alias = item.alias || item.title;
|
||||
return (
|
||||
<PinItem item={item} index={index} alias={alias} onPress={onPress} />
|
||||
);
|
||||
return <PinItem item={item} index={index} onPress={onPress} />;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -102,15 +99,16 @@ export const TagsSection = React.memo(
|
||||
);
|
||||
|
||||
export const PinItem = React.memo(
|
||||
function PinItem({ item, onPress, placeholder, alias }) {
|
||||
function PinItem({ item, onPress, placeholder }) {
|
||||
const { colors } = useThemeColors();
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
alias = item?.alias || item?.title;
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [headerTextState, setHeaderTextState] = useState(null);
|
||||
const primaryColors =
|
||||
headerTextState?.id === item.id ? colors.selected : colors.primary;
|
||||
|
||||
const isFocused = headerTextState?.id === item.id;
|
||||
const color =
|
||||
headerTextState?.id === item.id
|
||||
? colors.selected.accent
|
||||
@@ -177,7 +175,7 @@ export const PinItem = React.memo(
|
||||
</SheetWrapper>
|
||||
)}
|
||||
<PressableButton
|
||||
type={headerTextState?.id === item.id ? "selected" : "gray"}
|
||||
type={isFocused ? "selected" : "gray"}
|
||||
onLongPress={() => {
|
||||
if (placeholder) return;
|
||||
Properties.present(item);
|
||||
@@ -234,7 +232,7 @@ export const PinItem = React.memo(
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{headerTextState?.id === item.id ? (
|
||||
{isFocused ? (
|
||||
<Heading
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
@@ -242,7 +240,7 @@ export const PinItem = React.memo(
|
||||
color={primaryColors.heading}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{alias}
|
||||
{item.title}
|
||||
</Heading>
|
||||
) : (
|
||||
<Paragraph
|
||||
@@ -250,7 +248,7 @@ export const PinItem = React.memo(
|
||||
color={primaryColors.paragraph}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{alias}
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
@@ -261,7 +259,7 @@ export const PinItem = React.memo(
|
||||
},
|
||||
(prev, next) => {
|
||||
if (!next.item) return false;
|
||||
if (prev.alias !== next.alias) return false;
|
||||
if (prev.item.title !== next.item.title) return false;
|
||||
if (prev.item?.dateModified !== next.item?.dateModified) return false;
|
||||
if (prev.item?.id !== next.item?.id) return false;
|
||||
return true;
|
||||
|
||||
@@ -64,6 +64,7 @@ interface InputProps extends TextInputProps {
|
||||
color: ColorValue;
|
||||
onPress: () => void;
|
||||
testID?: string;
|
||||
size?: number;
|
||||
};
|
||||
buttons?: React.ReactNode;
|
||||
onBlurInput?: () => void;
|
||||
|
||||
@@ -20,11 +20,11 @@ import { isReminderActive } from "@notesnook/core/dist/collections/reminders";
|
||||
import React from "react";
|
||||
import { ViewStyle } from "react-native";
|
||||
|
||||
import { Reminder } from "../../../services/notifications";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button, ButtonProps } from "../button";
|
||||
import { getFormattedReminderTime } from "@notesnook/common";
|
||||
import { Reminder } from "@notesnook/core/dist/types";
|
||||
|
||||
export const ReminderTime = ({
|
||||
checkIsActive = true,
|
||||
|
||||
@@ -1,902 +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 Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import Share from "react-native-share";
|
||||
import { db } from "../common/database";
|
||||
import { AttachmentDialog } from "../components/attachments";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import NoteHistory from "../components/note-history";
|
||||
import { AddNotebookSheet } from "../components/sheets/add-notebook";
|
||||
import MoveNoteSheet from "../components/sheets/add-to";
|
||||
import ExportNotesSheet from "../components/sheets/export-notes";
|
||||
import { MoveNotes } from "../components/sheets/move-notes/movenote";
|
||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
openVault,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useMenuStore } from "../stores/use-menu-store";
|
||||
import useNavigationStore from "../stores/use-navigation-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { toggleDarkMode } from "../utils/colors";
|
||||
import {
|
||||
eOnTopicSheetUpdate,
|
||||
eOpenAddTopicDialog,
|
||||
eOpenLoginDialog
|
||||
} from "../utils/events";
|
||||
import { deleteItems } from "../utils/functions";
|
||||
import { sleep } from "../utils/time";
|
||||
|
||||
export const useActions = ({ close = () => null, item }) => {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||
db.shortcuts.exists(item.id)
|
||||
);
|
||||
const processingId = useRef();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [notifPinned, setNotifPinned] = useState(null);
|
||||
const alias = item.alias || item.title;
|
||||
const [defaultNotebook, setDefaultNotebook] = useState(
|
||||
db.settings.getDefaultNotebook()
|
||||
);
|
||||
|
||||
const isPublished =
|
||||
item.type === "note" && db.monographs.isPublished(item.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (item.id === null) return;
|
||||
checkNotifPinned();
|
||||
if (item.type !== "note") {
|
||||
setIsPinnedToMenu(db.shortcuts.exists(item.id));
|
||||
}
|
||||
}, [checkNotifPinned, item]);
|
||||
|
||||
const checkNotifPinned = useCallback(() => {
|
||||
let pinned = Notifications.getPinnedNotes();
|
||||
if (!pinned) {
|
||||
setNotifPinned(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let index = pinned.findIndex((notif) => notif.id === item.id);
|
||||
if (index !== -1) {
|
||||
setNotifPinned(pinned[index]);
|
||||
} else {
|
||||
setNotifPinned(null);
|
||||
}
|
||||
}, [item.id]);
|
||||
|
||||
const isNoteInTopic = () => {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (item.type !== "note" || currentScreen.name !== "TopicNotes") return;
|
||||
return (
|
||||
db.notes?.topicReferences?.get(currentScreen.id)?.indexOf(item.id) > -1
|
||||
);
|
||||
};
|
||||
|
||||
const isNoteInNotebook = () => {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
||||
|
||||
return !!db.relations
|
||||
.to(item, "notebook")
|
||||
.find((notebook) => notebook.id === currentScreen.id);
|
||||
};
|
||||
|
||||
const onUpdate = useCallback(
|
||||
async (type) => {
|
||||
if (type === "unpin") {
|
||||
await sleep(1000);
|
||||
await Notifications.get();
|
||||
checkNotifPinned();
|
||||
}
|
||||
},
|
||||
[checkNotifPinned]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("onUpdate", onUpdate);
|
||||
|
||||
return () => {
|
||||
eUnSubscribeEvent("onUpdate", onUpdate);
|
||||
};
|
||||
}, [item, onUpdate]);
|
||||
|
||||
function switchTheme() {
|
||||
toggleDarkMode();
|
||||
}
|
||||
|
||||
function addTo() {
|
||||
clearSelection(true);
|
||||
setSelectedItem(item);
|
||||
MoveNoteSheet.present(item);
|
||||
}
|
||||
|
||||
async function addToFavorites() {
|
||||
if (!item.id) return;
|
||||
close();
|
||||
await db.notes.note(item.id).favorite();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
|
||||
async function pinItem() {
|
||||
if (!item.id) return;
|
||||
close();
|
||||
let type = item.type;
|
||||
await db[`${type}s`][type](item.id).pin();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
|
||||
async function pinToNotifications() {
|
||||
if (!checkNoteSynced()) return;
|
||||
if (Platform.OS === "ios") return;
|
||||
|
||||
if (notifPinned !== null) {
|
||||
Notifications.remove(item.id, notifPinned.identifier);
|
||||
await sleep(1000);
|
||||
await Notifications.get();
|
||||
checkNotifPinned();
|
||||
return;
|
||||
}
|
||||
if (item.locked) {
|
||||
ToastEvent.show({
|
||||
heading: "Note is locked",
|
||||
type: "error",
|
||||
message: "Locked notes cannot be pinned to notifications",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
let text = await convertNoteToText(item, false);
|
||||
let html = text.replace(/\n/g, "<br />");
|
||||
Notifications.displayNotification({
|
||||
title: item.title,
|
||||
message: item.headline || text,
|
||||
subtitle: "",
|
||||
bigText: html,
|
||||
ongoing: true,
|
||||
actions: ["UNPIN"],
|
||||
id: item.id
|
||||
});
|
||||
await sleep(1000);
|
||||
await Notifications.get();
|
||||
checkNotifPinned();
|
||||
}
|
||||
|
||||
async function restoreTrashItem() {
|
||||
if (!checkNoteSynced()) return;
|
||||
close();
|
||||
await db.trash.restore(item.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
let type = item.type === "trash" ? item.itemType : item.type;
|
||||
ToastEvent.show({
|
||||
heading:
|
||||
type === "note"
|
||||
? "Note restored from trash"
|
||||
: "Notebook restored from trash",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
async function copyContent() {
|
||||
if (processingId.current === "copyContent") {
|
||||
ToastEvent.show({
|
||||
heading: "Please wait...",
|
||||
message: "We are preparing your note for copy to clipboard",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!checkNoteSynced()) return;
|
||||
if (item.locked) {
|
||||
close();
|
||||
await sleep(300);
|
||||
openVault({
|
||||
copyNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
title: "Copy note",
|
||||
description: "Unlock note to copy to clipboard."
|
||||
});
|
||||
} else {
|
||||
processingId.current = "copyContent";
|
||||
Clipboard.setString(await convertNoteToText(item));
|
||||
processingId.current = null;
|
||||
ToastEvent.show({
|
||||
heading: "Note copied to clipboard",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function publishNote() {
|
||||
if (!checkNoteSynced()) return;
|
||||
if (!user) {
|
||||
ToastEvent.show({
|
||||
heading: "Login required",
|
||||
message: "Login to publish note",
|
||||
context: "local",
|
||||
func: () => {
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
},
|
||||
actionText: "Login"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user?.isEmailConfirmed) {
|
||||
ToastEvent.show({
|
||||
heading: "Email is not verified",
|
||||
message: "Please verify your email first.",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (item.locked) {
|
||||
ToastEvent.show({
|
||||
heading: "Locked notes cannot be published",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
PublishNoteSheet.present(item);
|
||||
}
|
||||
|
||||
const checkNoteSynced = () => {
|
||||
if (!user) return true;
|
||||
if (item.type !== "note" || item.itemType !== "note") return true;
|
||||
let isTrash = item.itemType === "note";
|
||||
if (!isTrash && !db.notes.note(item.id).synced()) {
|
||||
ToastEvent.show({
|
||||
context: "local",
|
||||
heading: "Note not synced",
|
||||
message: "Please run sync before making changes",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isTrash && !db.trash.synced(item.id)) {
|
||||
ToastEvent.show({
|
||||
context: "local",
|
||||
heading: "Note not synced",
|
||||
message: "Please run sync before making changes",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
async function addToVault() {
|
||||
if (!item.id) return;
|
||||
if (!checkNoteSynced()) return;
|
||||
if (item.locked) {
|
||||
close();
|
||||
await sleep(300);
|
||||
openVault({
|
||||
item: item,
|
||||
novault: true,
|
||||
locked: true,
|
||||
permanant: true,
|
||||
title: "Unlock note",
|
||||
description: "Remove note from the vault."
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await db.vault.add(item.id);
|
||||
let note = db.notes.note(item.id).data;
|
||||
if (note.locked) {
|
||||
close();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
} catch (e) {
|
||||
close();
|
||||
await sleep(300);
|
||||
switch (e.message) {
|
||||
case db.vault.ERRORS.noVault:
|
||||
openVault({
|
||||
item: item,
|
||||
novault: false,
|
||||
title: "Create vault",
|
||||
description: "Set a password to create a vault and lock note."
|
||||
});
|
||||
break;
|
||||
case db.vault.ERRORS.vaultLocked:
|
||||
openVault({
|
||||
item: item,
|
||||
novault: true,
|
||||
locked: true,
|
||||
title: "Lock note",
|
||||
description: "Give access to vault to lock this note."
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createMenuShortcut() {
|
||||
close();
|
||||
try {
|
||||
if (isPinnedToMenu) {
|
||||
await db.shortcuts.remove(item.id);
|
||||
} else {
|
||||
if (item.type === "topic") {
|
||||
await db.shortcuts.add({
|
||||
item: {
|
||||
type: "topic",
|
||||
id: item.id,
|
||||
notebookId: item.notebookId
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await db.shortcuts.add({
|
||||
item: {
|
||||
type: item.type,
|
||||
id: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
setIsPinnedToMenu(db.shortcuts.exists(item.id));
|
||||
setMenuPins();
|
||||
} catch (e) {
|
||||
console.error("error", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function renameTag() {
|
||||
close();
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: "Rename tag",
|
||||
paragraph: "Change the title of the tag " + alias,
|
||||
positivePress: async (value) => {
|
||||
if (!value || value === "" || value.trimStart().length == 0) return;
|
||||
await db.tags.rename(item.id, db.tags.sanitize(value));
|
||||
setTimeout(() => {
|
||||
useTagStore.getState().setTags();
|
||||
useMenuStore.getState().setMenuPins();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
}, 1);
|
||||
},
|
||||
input: true,
|
||||
defaultValue: alias,
|
||||
inputPlaceholder: "Enter title of tag",
|
||||
positiveText: "Save"
|
||||
});
|
||||
}
|
||||
|
||||
async function shareNote() {
|
||||
if (processingId.current === "shareNote") {
|
||||
ToastEvent.show({
|
||||
heading: "Please wait...",
|
||||
message: "We are preparing your note for sharing",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!checkNoteSynced()) return;
|
||||
if (item.locked) {
|
||||
close();
|
||||
await sleep(300);
|
||||
openVault({
|
||||
item: item,
|
||||
novault: true,
|
||||
locked: true,
|
||||
share: true,
|
||||
title: "Share note",
|
||||
description: "Unlock note to share it."
|
||||
});
|
||||
} else {
|
||||
processingId.current = "shareNote";
|
||||
const convertedText = await convertNoteToText(item);
|
||||
processingId.current = null;
|
||||
Share.open({
|
||||
title: "Share note to",
|
||||
failOnCancel: false,
|
||||
message: convertedText
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteItem() {
|
||||
if (!checkNoteSynced()) return;
|
||||
close();
|
||||
if (item.type === "tag" || item.type === "reminder") {
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: `Delete ${item.type}`,
|
||||
paragraph:
|
||||
item.type === "reminder"
|
||||
? "This reminder will be removed"
|
||||
: "This tag will be removed from all notes.",
|
||||
positivePress: async () => {
|
||||
if (item.type === "reminder") {
|
||||
await db.reminders.remove(item.id);
|
||||
} else {
|
||||
await db.tags.remove(item.id);
|
||||
}
|
||||
setImmediate(() => {
|
||||
useTagStore.getState().setTags();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
});
|
||||
},
|
||||
positiveText: "Delete",
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.locked) {
|
||||
await sleep(300);
|
||||
openVault({
|
||||
deleteNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
title: "Delete note",
|
||||
description: "Unlock note to delete it."
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
close();
|
||||
await sleep(300);
|
||||
await deleteItems(item);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function removeNoteFromTopic() {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (currentScreen.name !== "TopicNotes") return;
|
||||
await db.notes.removeFromNotebook(
|
||||
{
|
||||
id: currentScreen.notebookId,
|
||||
topic: currentScreen.id
|
||||
},
|
||||
item.id
|
||||
);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(eOnTopicSheetUpdate);
|
||||
close();
|
||||
}
|
||||
|
||||
async function removeNoteFromNotebook() {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (currentScreen.name !== "Notebook") return;
|
||||
await db.relations.unlink({ type: "notebook", id: currentScreen.id }, item);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
}
|
||||
|
||||
async function deleteTrashItem() {
|
||||
if (!checkNoteSynced()) return;
|
||||
close();
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: "Permanent delete",
|
||||
paragraph: `Are you sure you want to delete this ${item.itemType} permanently from trash?`,
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: async () => {
|
||||
await db.trash.delete(item.id);
|
||||
setImmediate(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: "Permanently deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
});
|
||||
},
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
}
|
||||
|
||||
async function openHistory() {
|
||||
presentSheet({
|
||||
component: (ref) => <NoteHistory fwdRef={ref} note={item} />
|
||||
});
|
||||
}
|
||||
|
||||
async function showAttachments() {
|
||||
AttachmentDialog.present(item);
|
||||
}
|
||||
|
||||
async function exportNote() {
|
||||
ExportNotesSheet.present([item]);
|
||||
}
|
||||
|
||||
async function toggleLocalOnly() {
|
||||
if (!checkNoteSynced() || !user) return;
|
||||
db.notes.note(item.id).localOnly();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
}
|
||||
|
||||
const toggleReadyOnlyMode = async () => {
|
||||
await db.notes.note(item.id).readonly();
|
||||
let current = db.notes.note(item.id).data.readonly;
|
||||
if (useEditorStore.getState().currentEditingNote === item.id) {
|
||||
useEditorStore.getState().setReadonly(current);
|
||||
// tiny.call(EditorWebView, tiny.toogleReadMode(current ? 'readonly' : 'design'));
|
||||
}
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
};
|
||||
|
||||
const duplicateNote = async () => {
|
||||
if (!checkNoteSynced()) return;
|
||||
await db.notes.note(item.id).duplicate();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
};
|
||||
const actions = [
|
||||
{
|
||||
id: "notebooks",
|
||||
title: "Link Notebooks",
|
||||
icon: "book-outline",
|
||||
func: addTo
|
||||
},
|
||||
{
|
||||
id: "add-tag",
|
||||
title: "Add tags",
|
||||
icon: "pound",
|
||||
func: addTo
|
||||
},
|
||||
{
|
||||
id: "add-reminder",
|
||||
title: "Remind me",
|
||||
icon: "clock-plus-outline",
|
||||
func: () => {
|
||||
ReminderSheet.present(null, { id: item.id, type: "note" });
|
||||
},
|
||||
close: true
|
||||
},
|
||||
{
|
||||
id: "lock-unlock",
|
||||
title: item.locked ? "Unlock" : "Lock",
|
||||
icon: item.locked ? "lock-open-outline" : "key-outline",
|
||||
func: addToVault,
|
||||
on: item.locked
|
||||
},
|
||||
{
|
||||
id: "publish",
|
||||
title: isPublished ? "Published" : "Publish",
|
||||
icon: "cloud-upload-outline",
|
||||
on: isPublished,
|
||||
func: publishNote
|
||||
},
|
||||
|
||||
{
|
||||
id: "export",
|
||||
title: "Export",
|
||||
icon: "export",
|
||||
func: exportNote
|
||||
},
|
||||
{
|
||||
id: "move-notes",
|
||||
title: "Add notes",
|
||||
icon: "plus",
|
||||
func: async () => {
|
||||
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "pin",
|
||||
title: item.pinned ? "Unpin" : "Pin",
|
||||
icon: item.pinned ? "pin-off-outline" : "pin-outline",
|
||||
func: pinItem,
|
||||
close: false,
|
||||
check: true,
|
||||
on: item.pinned,
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
id: "favorite",
|
||||
title: item.favorite ? "Unfav" : "Fav",
|
||||
icon: item.favorite ? "star-off" : "star-outline",
|
||||
func: addToFavorites,
|
||||
close: false,
|
||||
check: true,
|
||||
on: item.favorite,
|
||||
pro: true,
|
||||
color: "orange"
|
||||
},
|
||||
{
|
||||
id: "pin-to-notifications",
|
||||
title:
|
||||
notifPinned !== null
|
||||
? "Unpin from notifications"
|
||||
: "Pin to notifications",
|
||||
icon: "message-badge-outline",
|
||||
on: notifPinned !== null,
|
||||
func: pinToNotifications
|
||||
},
|
||||
|
||||
{
|
||||
id: "edit-notebook",
|
||||
title: "Edit notebook",
|
||||
icon: "square-edit-outline",
|
||||
func: async () => {
|
||||
AddNotebookSheet.present(item);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "edit-topic",
|
||||
title: "Edit topic",
|
||||
icon: "square-edit-outline",
|
||||
func: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenAddTopicDialog, {
|
||||
notebookId: item.notebookId,
|
||||
toEdit: item
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "copy",
|
||||
title: "Copy",
|
||||
icon: "content-copy",
|
||||
func: copyContent
|
||||
},
|
||||
{
|
||||
id: "restore",
|
||||
title: "Restore " + item.itemType,
|
||||
icon: "delete-restore",
|
||||
func: restoreTrashItem
|
||||
},
|
||||
|
||||
{
|
||||
id: "add-shortcut",
|
||||
title: isPinnedToMenu ? "Remove Shortcut" : "Add Shortcut",
|
||||
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
|
||||
func: createMenuShortcut,
|
||||
close: false,
|
||||
check: true,
|
||||
on: isPinnedToMenu,
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
id: "rename-tag",
|
||||
title: "Rename tag",
|
||||
icon: "square-edit-outline",
|
||||
func: renameTag
|
||||
},
|
||||
{
|
||||
id: "share",
|
||||
title: "Share",
|
||||
icon: "share-variant",
|
||||
func: shareNote
|
||||
},
|
||||
{
|
||||
id: "read-only",
|
||||
title: "Readonly",
|
||||
icon: "pencil-lock",
|
||||
func: toggleReadyOnlyMode,
|
||||
on: item.readonly
|
||||
},
|
||||
{
|
||||
id: "local-only",
|
||||
title: "Local only",
|
||||
icon: "sync-off",
|
||||
func: toggleLocalOnly,
|
||||
on: item.localOnly
|
||||
},
|
||||
{
|
||||
id: "duplicate",
|
||||
title: "Duplicate",
|
||||
icon: "content-duplicate",
|
||||
func: duplicateNote
|
||||
},
|
||||
{
|
||||
id: "dark-mode",
|
||||
title: "Dark mode",
|
||||
icon: "theme-light-dark",
|
||||
func: switchTheme,
|
||||
switch: true,
|
||||
on: isDark ? true : false,
|
||||
close: false,
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
id: "edit-reminder",
|
||||
title: "Edit reminder",
|
||||
icon: "pencil",
|
||||
func: async () => {
|
||||
ReminderSheet.present(item);
|
||||
},
|
||||
close: false
|
||||
},
|
||||
|
||||
{
|
||||
id: "reminders",
|
||||
title: "Reminders",
|
||||
icon: "clock-outline",
|
||||
func: async () => {
|
||||
RelationsList.present({
|
||||
reference: item,
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: "Reminders",
|
||||
onAdd: () => ReminderSheet.present(null, item, true),
|
||||
button: {
|
||||
title: "Add",
|
||||
type: "accent",
|
||||
onPress: () => ReminderSheet.present(null, item, true),
|
||||
icon: "plus"
|
||||
}
|
||||
});
|
||||
},
|
||||
close: false
|
||||
},
|
||||
{
|
||||
id: "attachments",
|
||||
title: "Attachments",
|
||||
icon: "attachment",
|
||||
func: showAttachments
|
||||
},
|
||||
{
|
||||
id: "history",
|
||||
title: "History",
|
||||
icon: "history",
|
||||
func: openHistory
|
||||
},
|
||||
|
||||
{
|
||||
id: "default-notebook",
|
||||
title:
|
||||
defaultNotebook?.id === item.id
|
||||
? "Remove as default"
|
||||
: "Set as default",
|
||||
hidden: item.type !== "notebook",
|
||||
icon: "notebook",
|
||||
func: async () => {
|
||||
if (defaultNotebook?.id === item.id) {
|
||||
await db.settings.setDefaultNotebook();
|
||||
setDefaultNotebook();
|
||||
} else {
|
||||
const notebook = {
|
||||
id: item.id
|
||||
};
|
||||
await db.settings.setDefaultNotebook(notebook);
|
||||
setDefaultNotebook(notebook);
|
||||
}
|
||||
close();
|
||||
},
|
||||
on: defaultNotebook?.topic ? false : defaultNotebook?.id === item.id
|
||||
},
|
||||
{
|
||||
id: "default-topic",
|
||||
title:
|
||||
defaultNotebook?.id === item.id
|
||||
? "Remove as default"
|
||||
: "Set as default",
|
||||
hidden: item.type !== "topic",
|
||||
icon: "bookmark",
|
||||
func: async () => {
|
||||
if (defaultNotebook?.topic === item.id) {
|
||||
await db.settings.setDefaultNotebook();
|
||||
setDefaultNotebook();
|
||||
} else {
|
||||
const notebook = {
|
||||
id: item.notebookId,
|
||||
topic: item.id
|
||||
};
|
||||
await db.settings.setDefaultNotebook(notebook);
|
||||
setDefaultNotebook(notebook);
|
||||
}
|
||||
close();
|
||||
},
|
||||
on: defaultNotebook?.topic === item.id
|
||||
},
|
||||
{
|
||||
id: "disable-reminder",
|
||||
title: !item.disabled ? "Turn off reminder" : "Turn on reminder",
|
||||
icon: !item.disabled ? "bell-off-outline" : "bell",
|
||||
func: async () => {
|
||||
close();
|
||||
await db.reminders.add({
|
||||
...item,
|
||||
disabled: !item.disabled
|
||||
});
|
||||
Notifications.scheduleNotification(item);
|
||||
useRelationStore.getState().update();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "remove-from-topic",
|
||||
title: "Remove from topic",
|
||||
hidden: !isNoteInTopic(),
|
||||
icon: "minus-circle-outline",
|
||||
func: removeNoteFromTopic
|
||||
},
|
||||
{
|
||||
id: "remove-from-notebook",
|
||||
title: "Remove from notebook",
|
||||
hidden: !isNoteInNotebook(),
|
||||
icon: "minus-circle-outline",
|
||||
func: removeNoteFromNotebook
|
||||
},
|
||||
{
|
||||
id: "trash",
|
||||
title:
|
||||
item.type !== "notebook" && item.type !== "note"
|
||||
? "Delete " + item.type
|
||||
: "Move to trash",
|
||||
icon: "delete-outline",
|
||||
type: "error",
|
||||
func: deleteItem
|
||||
},
|
||||
{
|
||||
id: "delete",
|
||||
title: "Delete " + item.itemType,
|
||||
icon: "delete",
|
||||
func: deleteTrashItem
|
||||
}
|
||||
// {
|
||||
// id: "ReferencedIn",
|
||||
// title: "References",
|
||||
// icon: "link",
|
||||
// func: async () => {
|
||||
// close();
|
||||
// RelationsList.present({
|
||||
// reference: item,
|
||||
// referenceType: "note",
|
||||
// title: "Referenced in",
|
||||
// relationType: "to",
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
];
|
||||
|
||||
return actions;
|
||||
};
|
||||
1001
apps/mobile/app/hooks/use-actions.tsx
Normal file
1001
apps/mobile/app/hooks/use-actions.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,15 +17,28 @@ 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 { EV, EVENTS, SYNC_CHECK_IDS } from "@notesnook/core/dist/common";
|
||||
import {
|
||||
AttachmentsProgressEvent,
|
||||
EV,
|
||||
EVENTS,
|
||||
SYNC_CHECK_IDS,
|
||||
SyncProgressEvent,
|
||||
SyncStatusEvent
|
||||
} from "@notesnook/core/dist/common";
|
||||
import notifee from "@notifee/react-native";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import NetInfo, {
|
||||
NetInfoState,
|
||||
NetInfoSubscription
|
||||
} from "@react-native-community/netinfo";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
AppState,
|
||||
AppStateStatus,
|
||||
EmitterSubscription,
|
||||
Keyboard,
|
||||
Linking,
|
||||
NativeEventEmitter,
|
||||
NativeEventSubscription,
|
||||
NativeModules,
|
||||
Platform
|
||||
} from "react-native";
|
||||
@@ -33,6 +46,10 @@ import RNBootSplash from "react-native-bootsplash";
|
||||
import { checkVersion } from "react-native-check-version";
|
||||
import Config from "react-native-config";
|
||||
import * as RNIap from "react-native-iap";
|
||||
|
||||
import { User } from "@notesnook/core/dist/api/user-manager";
|
||||
import { EventManagerSubscription } from "@notesnook/core/dist/utils/event-manager";
|
||||
//@ts-ignore
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
@@ -47,7 +64,7 @@ import {
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
presentSheet
|
||||
@@ -84,7 +101,7 @@ import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { sleep } from "../utils/time";
|
||||
|
||||
const onCheckSyncStatus = async (type) => {
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
switch (type) {
|
||||
case SYNC_CHECK_IDS.sync:
|
||||
@@ -100,10 +117,16 @@ const onSyncAborted = () => {
|
||||
useUserStore.getState().setSyncing(false, SyncStatus.Failed);
|
||||
};
|
||||
|
||||
const onFileEncryptionProgress = ({ total, progress }) => {
|
||||
const onFileEncryptionProgress = ({
|
||||
total,
|
||||
progress
|
||||
}: {
|
||||
total: number;
|
||||
progress: number;
|
||||
}) => {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setEncryptionProgress((progress / total).toFixed(2));
|
||||
.setEncryptionProgress(Math.round(progress / total));
|
||||
};
|
||||
|
||||
const onDownloadingAttachmentProgress = (data) => {
|
||||
@@ -129,15 +152,15 @@ const onUserSessionExpired = async () => {
|
||||
eSendEvent(eLoginSessionExpired);
|
||||
};
|
||||
|
||||
const onAppOpenedFromURL = async (event) => {
|
||||
let url = event ? event.url : "";
|
||||
const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
const url = event.url;
|
||||
try {
|
||||
if (url.startsWith("https://app.notesnook.com/account/verified")) {
|
||||
await onUserEmailVerified();
|
||||
} else if (url.startsWith("ShareMedia://QuickNoteWidget")) {
|
||||
clearAppState();
|
||||
editorState().movedAway = false;
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
return;
|
||||
}
|
||||
@@ -147,7 +170,7 @@ const onAppOpenedFromURL = async (event) => {
|
||||
};
|
||||
|
||||
const onUserEmailVerified = async () => {
|
||||
let user = await db.user.getUser();
|
||||
const user = await db.user.getUser();
|
||||
useUserStore.getState().setUser(user);
|
||||
if (!user) return;
|
||||
SettingsService.set({
|
||||
@@ -160,8 +183,10 @@ const onUserEmailVerified = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onUserSubscriptionStatusChanged = async (userStatus) => {
|
||||
if (!PremiumService.get() && userStatus.type === 5) {
|
||||
const onUserSubscriptionStatusChanged = async (
|
||||
subscription: User["subscription"]
|
||||
) => {
|
||||
if (!PremiumService.get() && subscription.type === 5) {
|
||||
PremiumService.subscriptions.clear();
|
||||
Walkthrough.present("prouser", false, true);
|
||||
}
|
||||
@@ -169,7 +194,11 @@ const onUserSubscriptionStatusChanged = async (userStatus) => {
|
||||
useMessageStore.getState().setAnnouncement();
|
||||
};
|
||||
|
||||
const onRequestPartialSync = async (full, force, lastSyncTime) => {
|
||||
const onRequestPartialSync = async (
|
||||
full: boolean,
|
||||
force: boolean,
|
||||
lastSyncTime?: number | undefined
|
||||
) => {
|
||||
if (SettingsService.get().disableAutoSync) return;
|
||||
DatabaseLogger.info(
|
||||
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
|
||||
@@ -181,7 +210,7 @@ const onRequestPartialSync = async (full, force, lastSyncTime) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onLogout = async (reason) => {
|
||||
const onLogout = async (reason: string) => {
|
||||
DatabaseLogger.log("User Logged Out" + reason);
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
@@ -190,8 +219,8 @@ const onLogout = async (reason) => {
|
||||
|
||||
async function checkForShareExtensionLaunchedInBackground() {
|
||||
try {
|
||||
let notesAddedFromIntent = MMKV.getString("notesAddedFromIntent");
|
||||
let shareExtensionOpened = MMKV.getString("shareExtensionOpened");
|
||||
const notesAddedFromIntent = MMKV.getString("notesAddedFromIntent");
|
||||
const shareExtensionOpened = MMKV.getString("shareExtensionOpened");
|
||||
if (notesAddedFromIntent) {
|
||||
if (Platform.OS === "ios") {
|
||||
await db.initCollections();
|
||||
@@ -204,10 +233,10 @@ async function checkForShareExtensionLaunchedInBackground() {
|
||||
}
|
||||
|
||||
if (notesAddedFromIntent || shareExtensionOpened) {
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
const id = useEditorStore.getState().currentEditingNote;
|
||||
const note = id && db.notes.note(id)?.data;
|
||||
eSendEvent("webview_reset");
|
||||
setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||
MMKV.removeItem("shareExtensionOpened");
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -217,39 +246,30 @@ async function checkForShareExtensionLaunchedInBackground() {
|
||||
|
||||
async function saveEditorState() {
|
||||
if (editorState().currentlyEditing) {
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
const id = useEditorStore.getState().currentEditingNote;
|
||||
const note = id ? db.notes.note(id)?.data : undefined;
|
||||
|
||||
if (note?.locked) return;
|
||||
let state = JSON.stringify({
|
||||
const state = JSON.stringify({
|
||||
editing: editorState().currentlyEditing,
|
||||
note: note,
|
||||
movedAway: editorState().movedAway,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
MMKV.setString("appState", state);
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.Purchase} subscription
|
||||
*/
|
||||
const onSuccessfulSubscription = async (subscription) => {
|
||||
console.log(
|
||||
"Subscription success!",
|
||||
subscription.transactionId,
|
||||
subscription.obfuscatedAccountIdAndroid,
|
||||
subscription.obfuscatedProfileIdAndroid
|
||||
);
|
||||
|
||||
const onSuccessfulSubscription = async (
|
||||
subscription: RNIap.ProductPurchase | RNIap.SubscriptionPurchase
|
||||
) => {
|
||||
await PremiumService.subscriptions.set(subscription);
|
||||
await PremiumService.subscriptions.verify(subscription);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.PurchaseError} error
|
||||
*/
|
||||
const onSubscriptionError = async (error) => {
|
||||
ToastEvent.show({
|
||||
const onSubscriptionError = async (error: RNIap.PurchaseError) => {
|
||||
ToastManager.show({
|
||||
heading: "Failed to subscribe",
|
||||
type: "error",
|
||||
message: error.message,
|
||||
@@ -261,7 +281,6 @@ const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
|
||||
|
||||
export const useAppEvents = () => {
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
const setLoading = useNoteStore((state) => state.setLoading);
|
||||
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
|
||||
state.setLastSynced,
|
||||
state.setUser,
|
||||
@@ -270,17 +289,20 @@ export const useAppEvents = () => {
|
||||
]);
|
||||
|
||||
const syncedOnLaunch = useRef(false);
|
||||
const refValues = useRef({
|
||||
subsriptionSuccessListener: null,
|
||||
subsriptionErrorListener: null,
|
||||
isUserReady: false,
|
||||
prevState: null,
|
||||
showingDialog: false,
|
||||
removeInternetStateListener: null,
|
||||
isReconnecting: false,
|
||||
initialUrl: null,
|
||||
backupDidWait: false
|
||||
});
|
||||
|
||||
const refValues = useRef<
|
||||
Partial<{
|
||||
subsriptionSuccessListener: EmitterSubscription;
|
||||
subsriptionErrorListener: EmitterSubscription;
|
||||
isUserReady: boolean;
|
||||
prevState: AppStateStatus;
|
||||
showingDialog: boolean;
|
||||
removeInternetStateListener: NetInfoSubscription;
|
||||
isReconnecting: boolean;
|
||||
initialUrl: string;
|
||||
backupDidWait: boolean;
|
||||
}>
|
||||
>({});
|
||||
|
||||
const onSyncComplete = useCallback(async () => {
|
||||
initAfterSync();
|
||||
@@ -289,29 +311,131 @@ export const useAppEvents = () => {
|
||||
}, [setLastSynced]);
|
||||
|
||||
useEffect(() => {
|
||||
let eventSubscriptions = [];
|
||||
if (!loading) {
|
||||
const eventManager = db?.eventManager;
|
||||
eventSubscriptions = [
|
||||
eventManager?.subscribe(EVENTS.syncCompleted, onSyncComplete),
|
||||
eventManager?.subscribe(
|
||||
EVENTS.databaseSyncRequested,
|
||||
onRequestPartialSync
|
||||
)
|
||||
];
|
||||
}
|
||||
if (loading) return;
|
||||
|
||||
let subscriptions: EventManagerSubscription[] = [];
|
||||
const eventManager = db.eventManager;
|
||||
subscriptions = [
|
||||
eventManager?.subscribe(EVENTS.syncCompleted, onSyncComplete),
|
||||
eventManager?.subscribe(
|
||||
EVENTS.databaseSyncRequested,
|
||||
onRequestPartialSync
|
||||
)
|
||||
];
|
||||
|
||||
return () => {
|
||||
eventSubscriptions.forEach((sub) => sub?.unsubscribe?.());
|
||||
subscriptions.forEach((sub) => sub?.unsubscribe?.());
|
||||
};
|
||||
}, [loading, onSyncComplete]);
|
||||
|
||||
const subscribeToPurchaseListeners = useCallback(async () => {
|
||||
if (Platform.OS === "android") {
|
||||
try {
|
||||
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
||||
} catch (e) {}
|
||||
}
|
||||
refValues.current.subsriptionSuccessListener =
|
||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||
refValues.current.subsriptionErrorListener =
|
||||
RNIap.purchaseErrorListener(onSubscriptionError);
|
||||
}, []);
|
||||
|
||||
const unsubscribePurchaseListeners = () => {
|
||||
if (refValues.current?.subsriptionSuccessListener) {
|
||||
refValues.current.subsriptionSuccessListener?.remove();
|
||||
refValues.current.subsriptionSuccessListener = undefined;
|
||||
}
|
||||
if (refValues.current?.subsriptionErrorListener) {
|
||||
refValues.current.subsriptionErrorListener?.remove();
|
||||
refValues.current.subsriptionErrorListener = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const checkAutoBackup = useCallback(async () => {
|
||||
const { appLocked, syncing } = useUserStore.getState();
|
||||
|
||||
if (appLocked || syncing) {
|
||||
refValues.current.backupDidWait = true;
|
||||
return;
|
||||
}
|
||||
const user = await db.user.getUser();
|
||||
if (PremiumService.get() && user) {
|
||||
if (
|
||||
await BackupService.checkBackupRequired(SettingsService.get().reminder)
|
||||
) {
|
||||
if (
|
||||
!SettingsService.get().backupDirectoryAndroid &&
|
||||
Platform.OS === "android"
|
||||
)
|
||||
return;
|
||||
sleep(2000).then(() => BackupService.run());
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onUserUpdated = useCallback(
|
||||
async (isLogin?: boolean) => {
|
||||
let user;
|
||||
try {
|
||||
user = await db.user.getUser();
|
||||
await PremiumService.setPremiumStatus();
|
||||
setLastSynced(await db.lastSynced());
|
||||
await useDragState.getState().init();
|
||||
if (!user) return;
|
||||
|
||||
const isUserEmailConfirmed = SettingsService.get().userEmailConfirmed;
|
||||
setUser(user);
|
||||
if (SettingsService.get().sessionExpired) {
|
||||
syncedOnLaunch.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
subscribeToPurchaseListeners();
|
||||
if (!isLogin) {
|
||||
user = await db.user.fetchUser();
|
||||
setUser(user);
|
||||
} else {
|
||||
SettingsService.set({
|
||||
encryptedBackup: true
|
||||
});
|
||||
}
|
||||
|
||||
await PremiumService.setPremiumStatus();
|
||||
if (user?.isEmailConfirmed && !isUserEmailConfirmed) {
|
||||
setTimeout(() => {
|
||||
onUserEmailVerified();
|
||||
}, 1000);
|
||||
SettingsService.set({
|
||||
userEmailConfirmed: true
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
ToastManager.error(e as Error, "Error updating user", "global");
|
||||
}
|
||||
|
||||
user = await db.user.getUser();
|
||||
if (
|
||||
user?.isEmailConfirmed &&
|
||||
!SettingsService.get().recoveryKeySaved &&
|
||||
!useMessageStore.getState().message?.visible
|
||||
) {
|
||||
setRecoveryKeyMessage();
|
||||
}
|
||||
if (!user?.isEmailConfirmed) setEmailVerifyMessage();
|
||||
refValues.current.isUserReady = true;
|
||||
|
||||
syncedOnLaunch.current = true;
|
||||
if (!isLogin) {
|
||||
checkAutoBackup();
|
||||
}
|
||||
},
|
||||
[subscribeToPurchaseListeners, setLastSynced, setUser, checkAutoBackup]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let eventSubscriptions = [
|
||||
Linking.addEventListener("url", onAppOpenedFromURL),
|
||||
SodiumEventEmitter.addListener(
|
||||
"onSodiumProgress",
|
||||
onFileEncryptionProgress
|
||||
),
|
||||
const subscriptions = [
|
||||
EV.subscribe(EVENTS.syncCheckStatus, onCheckSyncStatus),
|
||||
EV.subscribe(EVENTS.syncAborted, onSyncAborted),
|
||||
EV.subscribe(EVENTS.appRefreshRequested, onSyncComplete),
|
||||
@@ -336,21 +460,28 @@ export const useAppEvents = () => {
|
||||
eSubscribeEvent(eUserLoggedIn, onUserUpdated)
|
||||
];
|
||||
|
||||
const emitterSubscriptions = [
|
||||
Linking.addEventListener("url", onAppOpenedFromURL),
|
||||
SodiumEventEmitter.addListener(
|
||||
"onSodiumProgress",
|
||||
onFileEncryptionProgress
|
||||
)
|
||||
];
|
||||
|
||||
return () => {
|
||||
eventSubscriptions.forEach(
|
||||
(sub) => sub?.remove?.() || sub?.unsubscribe?.()
|
||||
);
|
||||
emitterSubscriptions.forEach((sub) => sub?.remove?.());
|
||||
subscriptions.forEach((sub) => sub?.unsubscribe?.());
|
||||
EV.unsubscribeAll();
|
||||
};
|
||||
}, [onSyncComplete, onUserUpdated]);
|
||||
|
||||
useEffect(() => {
|
||||
const onInternetStateChanged = async (state) => {
|
||||
const onInternetStateChanged = async (state: NetInfoState) => {
|
||||
if (!syncedOnLaunch.current) return;
|
||||
reconnectSSE(state);
|
||||
};
|
||||
|
||||
const onAppStateChanged = async (state) => {
|
||||
const onAppStateChanged = async (state: AppStateStatus) => {
|
||||
if (state === "active") {
|
||||
notifee.setBadgeCount(0);
|
||||
updateStatusBarColor();
|
||||
@@ -373,7 +504,7 @@ export const useAppEvents = () => {
|
||||
let user = await db.user.getUser();
|
||||
if (user && !user?.isEmailConfirmed) {
|
||||
try {
|
||||
let user = await db.user.fetchUser();
|
||||
user = await db.user.fetchUser();
|
||||
if (user?.isEmailConfirmed) {
|
||||
onUserEmailVerified();
|
||||
}
|
||||
@@ -382,8 +513,8 @@ export const useAppEvents = () => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
const id = useEditorStore.getState().currentEditingNote;
|
||||
const note = id ? db.notes.note(id)?.data : undefined;
|
||||
if (
|
||||
note?.locked &&
|
||||
SettingsService.get().appLockMode === "background"
|
||||
@@ -414,10 +545,12 @@ export const useAppEvents = () => {
|
||||
|
||||
if (!refValues.current.initialUrl) {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
refValues.current.initialUrl = url;
|
||||
if (url) {
|
||||
refValues.current.initialUrl = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
let sub;
|
||||
let sub: NativeEventSubscription;
|
||||
if (!loading && !appLocked) {
|
||||
setTimeout(() => {
|
||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||
@@ -438,120 +571,10 @@ export const useAppEvents = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
refValues.current?.removeInternetStateListener();
|
||||
sub?.remove();
|
||||
unSubscribeFromIAPListeners();
|
||||
unsubscribePurchaseListeners();
|
||||
};
|
||||
}, [loading, appLocked, checkAutoBackup]);
|
||||
|
||||
const onUserUpdated = useCallback(
|
||||
async (login) => {
|
||||
let user;
|
||||
try {
|
||||
user = await db.user.getUser();
|
||||
await PremiumService.setPremiumStatus();
|
||||
setLastSynced(await db.lastSynced());
|
||||
await useDragState.getState().init();
|
||||
if (!user) return;
|
||||
|
||||
let userEmailConfirmed = SettingsService.get().userEmailConfirmed;
|
||||
setUser(user);
|
||||
if (SettingsService.get().sessionExpired) {
|
||||
syncedOnLaunch.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
if (!login) {
|
||||
user = await db.user.fetchUser();
|
||||
setUser(user);
|
||||
} else {
|
||||
SettingsService.set({
|
||||
encryptedBackup: true
|
||||
});
|
||||
}
|
||||
|
||||
await PremiumService.setPremiumStatus();
|
||||
if (user?.isEmailConfirmed && !userEmailConfirmed) {
|
||||
setTimeout(() => {
|
||||
onUserEmailVerified();
|
||||
}, 1000);
|
||||
SettingsService.set({
|
||||
userEmailConfirmed: true
|
||||
});
|
||||
}
|
||||
|
||||
subscribeToIAPListeners();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
|
||||
ToastEvent.error(e, "An error occurred", "global");
|
||||
}
|
||||
|
||||
user = await db.user.getUser();
|
||||
if (
|
||||
user?.isEmailConfirmed &&
|
||||
!SettingsService.get().recoveryKeySaved &&
|
||||
!useMessageStore.getState().message?.visible
|
||||
) {
|
||||
setRecoveryKeyMessage();
|
||||
}
|
||||
if (!user?.isEmailConfirmed) setEmailVerifyMessage();
|
||||
refValues.current.isUserReady = true;
|
||||
|
||||
syncedOnLaunch.current = true;
|
||||
if (!login) {
|
||||
checkAutoBackup();
|
||||
}
|
||||
},
|
||||
[subscribeToIAPListeners, setLastSynced, setUser, checkAutoBackup]
|
||||
);
|
||||
|
||||
const subscribeToIAPListeners = useCallback(async () => {
|
||||
if (Platform.OS === "android") {
|
||||
try {
|
||||
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
||||
} catch (e) {
|
||||
e;
|
||||
}
|
||||
}
|
||||
refValues.current.subsriptionSuccessListener =
|
||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||
refValues.current.subsriptionErrorListener =
|
||||
RNIap.purchaseErrorListener(onSubscriptionError);
|
||||
}, []);
|
||||
|
||||
const unSubscribeFromIAPListeners = () => {
|
||||
if (refValues.current?.subsriptionSuccessListener) {
|
||||
refValues.current.subsriptionSuccessListener?.remove();
|
||||
refValues.current.subsriptionSuccessListener = null;
|
||||
}
|
||||
if (refValues.current?.subsriptionErrorListener) {
|
||||
refValues.current.subsriptionErrorListener?.remove();
|
||||
refValues.current.subsriptionErrorListener = null;
|
||||
}
|
||||
};
|
||||
|
||||
const checkAutoBackup = useCallback(async () => {
|
||||
const { appLocked, syncing } = useUserStore.getState();
|
||||
|
||||
if (appLocked || syncing) {
|
||||
refValues.current.backupDidWait = true;
|
||||
return;
|
||||
}
|
||||
const user = await db.user.getUser();
|
||||
if (PremiumService.get() && user) {
|
||||
if (
|
||||
await BackupService.checkBackupRequired(SettingsService.get().reminder)
|
||||
) {
|
||||
if (
|
||||
!SettingsService.get().backupDirectoryAndroid &&
|
||||
Platform.OS === "android"
|
||||
)
|
||||
return;
|
||||
sleep(2000).then(() => BackupService.run());
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appLocked && !syncing && refValues.current.backupDidWait) {
|
||||
refValues.current.backupDidWait = false;
|
||||
@@ -559,7 +582,7 @@ export const useAppEvents = () => {
|
||||
}
|
||||
}, [appLocked, syncing, checkAutoBackup]);
|
||||
|
||||
async function reconnectSSE(connection) {
|
||||
async function reconnectSSE(connection?: NetInfoState) {
|
||||
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
|
||||
return;
|
||||
|
||||
@@ -581,7 +604,7 @@ export const useAppEvents = () => {
|
||||
connectionState = await NetInfo.fetch();
|
||||
}
|
||||
|
||||
let user = await db.user.getUser();
|
||||
const user = await db.user.getUser();
|
||||
if (
|
||||
user &&
|
||||
connectionState.isConnected &&
|
||||
@@ -608,7 +631,7 @@ export const useAppEvents = () => {
|
||||
await db.init();
|
||||
}
|
||||
initialize();
|
||||
setLoading(false);
|
||||
useNoteStore.getState().setLoading(false);
|
||||
},
|
||||
disableClosing: true
|
||||
});
|
||||
@@ -629,20 +652,24 @@ export const useAppEvents = () => {
|
||||
if (!db.isInitialized) {
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
await db.init();
|
||||
try {
|
||||
await db.init();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
if (IsDatabaseMigrationRequired()) return;
|
||||
initialize();
|
||||
useNoteStore.getState().setLoading(false);
|
||||
Walkthrough.init();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
ToastEvent.error(e, "Error initializing database", "global");
|
||||
DatabaseLogger.error(e as Error);
|
||||
ToastManager.error(e as Error, "Error initializing database", "global");
|
||||
}
|
||||
}, [IsDatabaseMigrationRequired]);
|
||||
|
||||
useEffect(() => {
|
||||
let sub;
|
||||
let sub: () => void;
|
||||
if (appLocked) {
|
||||
const sub = useUserStore.subscribe((state) => {
|
||||
if (
|
||||
@@ -681,7 +708,7 @@ const doAppLoadActions = async () => {
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
if (SettingsService.get().introCompleted) {
|
||||
useMessageStore.subscribe((state) => {
|
||||
let dialogs = state.dialogs;
|
||||
const dialogs = state.dialogs;
|
||||
if (dialogs.length > 0) {
|
||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||
}
|
||||
@@ -706,7 +733,7 @@ const checkAppUpdateAvailable = async () => {
|
||||
};
|
||||
|
||||
const checkForRateAppRequest = async () => {
|
||||
let rateApp = SettingsService.get().rateApp;
|
||||
const rateApp = SettingsService.get().rateApp as number;
|
||||
if (
|
||||
rateApp &&
|
||||
rateApp < Date.now() &&
|
||||
@@ -17,6 +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 { useThemeColors } from "@notesnook/theme";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import * as React from "react";
|
||||
@@ -29,7 +30,7 @@ import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
|
||||
import { hideAllTooltips } from "../hooks/use-tooltip";
|
||||
import Favorites from "../screens/favorites";
|
||||
import Home from "../screens/home";
|
||||
import Notebook from "../screens/notebook";
|
||||
import NotebookScreen from "../screens/notebook";
|
||||
import Notebooks from "../screens/notebooks";
|
||||
import { ColoredNotes } from "../screens/notes/colored";
|
||||
import { Monographs } from "../screens/notes/monographs";
|
||||
@@ -47,21 +48,11 @@ import useNavigationStore from "../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../stores/use-notes-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import Auth from "../components/auth";
|
||||
|
||||
const NativeStack = createNativeStackNavigator();
|
||||
const IntroStack = createNativeStackNavigator();
|
||||
|
||||
/**
|
||||
* Intro Stack:
|
||||
*
|
||||
* Welcome Page
|
||||
* Select Privacy Mode Page
|
||||
* Login/Signup Page
|
||||
*
|
||||
*/
|
||||
|
||||
const IntroStackNavigator = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const height = useSettingStore((state) => state.dimensions.height);
|
||||
@@ -79,7 +70,6 @@ const IntroStackNavigator = () => {
|
||||
initialRouteName={"Intro"}
|
||||
>
|
||||
<NativeStack.Screen name="Intro" component={Intro} />
|
||||
<NativeStack.Screen name="Auth" component={Auth} />
|
||||
<NativeStack.Screen name="AppLock" component={AppLock} />
|
||||
</IntroStack.Navigator>
|
||||
);
|
||||
@@ -179,7 +169,7 @@ const _Tabs = () => {
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Notebook"
|
||||
component={Notebook}
|
||||
component={NotebookScreen}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
|
||||
@@ -99,12 +99,12 @@ const _TabsHolder = () => {
|
||||
clearAppState();
|
||||
if (!tabBarRef.current) {
|
||||
await sleep(3000);
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
editorState().movedAway = false;
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
return;
|
||||
}
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
editorState().movedAway = false;
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
}
|
||||
@@ -494,7 +494,9 @@ const onChangeTab = async (obj) => {
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
if (!editorState().currentlyEditing) {
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, {
|
||||
newNote: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (obj.from === 2) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import { eSubscribeEvent } from "../../services/event-manager";
|
||||
import { useEditorStore } from "../../stores/use-editor-store";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { NoteType } from "../../utils/types";
|
||||
import EditorOverlay from "./loading";
|
||||
import { EDITOR_URI } from "./source";
|
||||
import { EditorProps, useEditorType } from "./tiptap/types";
|
||||
@@ -180,9 +179,9 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
|
||||
const onPress = async () => {
|
||||
if (editor.note.current) {
|
||||
await db.notes?.note(editor.note.current.id).readonly();
|
||||
editor.note.current = db.notes?.note(editor.note.current.id)
|
||||
.data as NoteType;
|
||||
await db.notes.note(editor.note.current.id)?.readonly();
|
||||
editor.note.current = db.notes?.note(editor.note.current.id)?.data;
|
||||
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,9 +25,9 @@ import { EdgeInsets } from "react-native-safe-area-context";
|
||||
import WebView from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { Settings } from "./types";
|
||||
import { getResponse, randId, textInput } from "./utils";
|
||||
import { Note } from "@notesnook/core/dist/types";
|
||||
|
||||
type Action = { job: string; id: string };
|
||||
|
||||
@@ -166,20 +166,19 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
`);
|
||||
};
|
||||
|
||||
setTags = async (note: NoteType | null | undefined) => {
|
||||
setTags = async (note: Note | null | undefined) => {
|
||||
if (!note) return;
|
||||
const tags = !note.tags
|
||||
? []
|
||||
: note.tags
|
||||
.map((t: string) =>
|
||||
db.tags?.tag(t)
|
||||
? { title: db.tags.tag(t).title, alias: db.tags.tag(t).alias }
|
||||
: null
|
||||
)
|
||||
.filter((t) => t !== null);
|
||||
const tags = db.relations.to(note, "tag").resolved();
|
||||
await this.doAsync(`
|
||||
if (typeof editorTags !== "undefined" && editorTags.current) {
|
||||
editorTags.current.setTags(${JSON.stringify(tags)});
|
||||
editorTags.current.setTags(${JSON.stringify(
|
||||
tags.map((tag) => ({
|
||||
title: tag.title,
|
||||
alias: tag.title,
|
||||
id: tag.id,
|
||||
type: tag.type
|
||||
}))
|
||||
)});
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ import { launchCamera, launchImageLibrary } from "react-native-image-picker";
|
||||
import { db } from "../../../common/database";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
@@ -75,7 +75,7 @@ const file = async (fileOptions) => {
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
|
||||
if (file.size > FILE_SIZE_LIMIT) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
message: "The maximum allowed size per file is 500 MB",
|
||||
type: "error"
|
||||
@@ -84,7 +84,7 @@ const file = async (fileOptions) => {
|
||||
}
|
||||
|
||||
if (file.copyError) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Failed to open file",
|
||||
message: file.copyError,
|
||||
type: "error",
|
||||
@@ -125,7 +125,7 @@ const file = async (fileOptions) => {
|
||||
eSendEvent(eCloseSheet);
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
@@ -147,7 +147,7 @@ const camera = async (options) => {
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
@@ -170,7 +170,7 @@ const gallery = async (options) => {
|
||||
(response) => handleImageResponse(response, options)
|
||||
);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
message: "You need internet access to attach a file",
|
||||
type: "error",
|
||||
@@ -216,7 +216,7 @@ const handleImageResponse = async (response, options) => {
|
||||
|
||||
let image = response.assets[0];
|
||||
if (image.fileSize > IMAGE_SIZE_LIMIT) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
title: "File too large",
|
||||
message: "The maximum allowed size per image is 50 MB",
|
||||
type: "error"
|
||||
@@ -262,7 +262,7 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
let encryptionInfo;
|
||||
|
||||
if (options?.hash && options.hash !== hash) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Please select the same file for reuploading",
|
||||
message: `Expected hash ${options.hash} but got ${hash}.`,
|
||||
type: "error",
|
||||
|
||||
@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { ToolbarGroupDefinition } from "@notesnook/editor/dist/toolbar/types";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { useEditor } from "./use-editor";
|
||||
import { Note } from "@notesnook/core/dist/types";
|
||||
export type useEditorType = ReturnType<typeof useEditor>;
|
||||
|
||||
export type EditorState = {
|
||||
@@ -72,37 +72,18 @@ export type EditorMessage = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type Note = {
|
||||
[name: string]: unknown;
|
||||
id: string | null;
|
||||
type: string;
|
||||
contentId: string;
|
||||
title: string;
|
||||
locked: boolean;
|
||||
conflicted: boolean;
|
||||
dateEdited: number;
|
||||
headline: string;
|
||||
};
|
||||
|
||||
export type Content = {
|
||||
data?: string;
|
||||
type: string;
|
||||
noteId: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type SavePayload = {
|
||||
title?: string;
|
||||
id?: string | null;
|
||||
data?: Content["data"];
|
||||
type?: Content["type"];
|
||||
id?: string;
|
||||
data?: string;
|
||||
type?: "tiptap";
|
||||
sessionId?: string | null;
|
||||
sessionHistoryId?: number;
|
||||
ignoreEdit: boolean;
|
||||
};
|
||||
|
||||
export type AppState = {
|
||||
note?: NoteType;
|
||||
note?: Note;
|
||||
editing: boolean;
|
||||
movedAway: boolean;
|
||||
timestamp: number;
|
||||
|
||||
@@ -38,7 +38,7 @@ import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
import ReminderSheet from "../../../components/sheets/reminder";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
@@ -59,7 +59,6 @@ import {
|
||||
} from "../../../utils/events";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { useDragState } from "../../settings/editor/state";
|
||||
import { EventTypes } from "./editor-events";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
@@ -67,11 +66,13 @@ import { EditorEvents, editorState } from "./utils";
|
||||
import { useNoteStore } from "../../../stores/use-notes-store";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { ItemReference } from "@notesnook/core/dist/types";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
|
||||
const publishNote = async (editor: useEditorType) => {
|
||||
const user = useUserStore.getState().user;
|
||||
if (!user) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Login required",
|
||||
message: "Login to publish",
|
||||
context: "global",
|
||||
@@ -84,7 +85,7 @@ const publishNote = async (editor: useEditorType) => {
|
||||
}
|
||||
|
||||
if (!user?.isEmailConfirmed) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Email not verified",
|
||||
message: "Please verify your email first.",
|
||||
context: "global"
|
||||
@@ -93,9 +94,9 @@ const publishNote = async (editor: useEditorType) => {
|
||||
}
|
||||
const currentNote = editor?.note?.current;
|
||||
if (currentNote?.id) {
|
||||
const note = db.notes?.note(currentNote.id)?.data as NoteType;
|
||||
const note = db.notes?.note(currentNote.id)?.data;
|
||||
if (note?.locked) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Locked notes cannot be published",
|
||||
type: "error",
|
||||
context: "global"
|
||||
@@ -112,7 +113,7 @@ const publishNote = async (editor: useEditorType) => {
|
||||
const showActionsheet = async (editor: useEditorType) => {
|
||||
const currentNote = editor?.note?.current;
|
||||
if (currentNote?.id) {
|
||||
const note = db.notes?.note(currentNote.id)?.data as NoteType;
|
||||
const note = db.notes?.note(currentNote.id)?.data;
|
||||
|
||||
if (editorState().isFocused || editorState().isFocused) {
|
||||
editorState().isFocused = true;
|
||||
@@ -120,7 +121,7 @@ const showActionsheet = async (editor: useEditorType) => {
|
||||
const { Properties } = require("../../../components/properties/index.js");
|
||||
Properties.present(note, ["Dark Mode"]);
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Start writing to create a new note",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -373,7 +374,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.reminders:
|
||||
if (!editor.note.current) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Create a note first to add a reminder",
|
||||
type: "success"
|
||||
});
|
||||
@@ -390,7 +391,7 @@ export const useEditorEvents = (
|
||||
break;
|
||||
case EventTypes.newtag:
|
||||
if (!editor.note.current) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Create a note first to add a tag",
|
||||
type: "success"
|
||||
});
|
||||
@@ -401,11 +402,11 @@ export const useEditorEvents = (
|
||||
case EventTypes.tag:
|
||||
if (editorMessage.value) {
|
||||
if (!editor.note.current) return;
|
||||
db.notes
|
||||
?.note(editor.note.current?.id)
|
||||
.untag(editorMessage.value)
|
||||
db.relations
|
||||
.unlink(editorMessage.value as ItemReference, editor.note.current)
|
||||
.then(async () => {
|
||||
useTagStore.getState().setTags();
|
||||
useRelationStore.getState().update();
|
||||
await editor.commands.setTags(editor.note.current);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
});
|
||||
|
||||
@@ -18,7 +18,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import {
|
||||
isEncryptedContent,
|
||||
isUnencryptedContent
|
||||
} from "@notesnook/core/dist/collections/content";
|
||||
import { NoteContent } from "@notesnook/core/dist/collections/session-content";
|
||||
import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import {
|
||||
ContentItem,
|
||||
ContentType,
|
||||
Note,
|
||||
UnencryptedContentItem,
|
||||
isDeleted
|
||||
} from "@notesnook/core/dist/types";
|
||||
import { useThemeEngineStore } from "@notesnook/theme";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import WebView from "react-native-webview";
|
||||
@@ -40,10 +52,9 @@ import { useNoteStore } from "../../../stores/use-notes-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { eClearEditor, eOnLoadNote } from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { onNoteCreated } from "../../notes/common";
|
||||
import Commands from "./commands";
|
||||
import { Content, EditorState, Note, SavePayload } from "./types";
|
||||
import { EditorState, SavePayload } from "./types";
|
||||
import {
|
||||
EditorEvents,
|
||||
clearAppState,
|
||||
@@ -65,8 +76,15 @@ export const useEditor = (
|
||||
const [loading, setLoading] = useState(false);
|
||||
const sessionIdRef = useRef(makeSessionId());
|
||||
const editorRef = useRef<WebView>(null);
|
||||
const currentNote = useRef<NoteType | null>();
|
||||
const currentContent = useRef<Content | null>();
|
||||
const currentNote = useRef<
|
||||
| (Note & {
|
||||
content?: NoteContent<false> & {
|
||||
isPreview?: boolean;
|
||||
};
|
||||
})
|
||||
| null
|
||||
>();
|
||||
const currentContent = useRef<Partial<UnencryptedContentItem> | null>();
|
||||
const timers = useRef<{ [name: string]: NodeJS.Timeout }>({});
|
||||
const commands = useMemo(() => new Commands(editorRef), [editorRef]);
|
||||
const sessionHistoryId = useRef<number>();
|
||||
@@ -128,7 +146,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;
|
||||
currentContent.current = null;
|
||||
sessionHistoryId.current = undefined;
|
||||
@@ -178,7 +196,7 @@ export const useEditor = (
|
||||
await reset();
|
||||
return;
|
||||
}
|
||||
let note = id ? (db.notes?.note(id)?.data as Note) : null;
|
||||
let note = id ? db.notes?.note(id)?.data : undefined;
|
||||
const locked = note?.locked;
|
||||
if (note?.conflicted) return;
|
||||
|
||||
@@ -189,9 +207,14 @@ export const useEditor = (
|
||||
currentSessionHistoryId = sessionHistoryId.current;
|
||||
}
|
||||
|
||||
const noteData: Partial<Note> = {
|
||||
const noteData: Partial<Note> & {
|
||||
sessionId?: string;
|
||||
content?: NoteContent<false>;
|
||||
} = {
|
||||
id,
|
||||
sessionId: isContentInvalid(data) ? null : currentSessionHistoryId
|
||||
sessionId: isContentInvalid(data)
|
||||
? undefined
|
||||
: (currentSessionHistoryId as any)
|
||||
};
|
||||
|
||||
noteData.title = title;
|
||||
@@ -204,13 +227,13 @@ export const useEditor = (
|
||||
if (data) {
|
||||
noteData.content = {
|
||||
data: data,
|
||||
type: type
|
||||
type: type as ContentType
|
||||
};
|
||||
}
|
||||
if (!locked) {
|
||||
id = await db.notes?.add(noteData);
|
||||
if (!note && id) {
|
||||
currentNote.current = db.notes?.note(id).data as NoteType;
|
||||
currentNote.current = db.notes?.note(id)?.data;
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
if (!state.current.onNoteCreated && defaultNotebook) {
|
||||
onNoteCreated(id, {
|
||||
@@ -223,7 +246,7 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
if (!noteData.title) {
|
||||
postMessage(EditorEvents.title, currentNote.current.title);
|
||||
postMessage(EditorEvents.title, currentNote.current?.title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +275,10 @@ export const useEditor = (
|
||||
}
|
||||
if (id && sessionIdRef.current === currentSessionId) {
|
||||
note = db.notes?.note(id)?.data as Note;
|
||||
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
|
||||
await commands.setStatus(
|
||||
getFormattedDate(note.dateEdited, "date-time"),
|
||||
"Saved"
|
||||
);
|
||||
|
||||
lastContentChangeTime.current = note.dateEdited;
|
||||
|
||||
@@ -276,27 +302,38 @@ export const useEditor = (
|
||||
[commands, isDefaultEditor, postMessage, readonly, reset]
|
||||
);
|
||||
|
||||
const loadContent = useCallback(async (note: NoteType) => {
|
||||
currentNote.current = note;
|
||||
if (note.locked || note.content) {
|
||||
currentContent.current = {
|
||||
data: note.content?.data,
|
||||
type: note.content?.type || "tiptap",
|
||||
noteId: currentNote.current?.id as string
|
||||
};
|
||||
} else {
|
||||
if (!note.contentId) return;
|
||||
currentContent.current = await db.content?.raw(note.contentId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadNote = useCallback(
|
||||
const loadContent = useCallback(
|
||||
async (
|
||||
item: Omit<NoteType, "type"> & {
|
||||
type: "note" | "new";
|
||||
forced?: boolean;
|
||||
note: Note & {
|
||||
content?: NoteContent<false>;
|
||||
}
|
||||
) => {
|
||||
currentNote.current = note;
|
||||
if ((note.locked || note.content) && note.content?.data) {
|
||||
currentContent.current = {
|
||||
data: note.content?.data,
|
||||
type: note.content?.type || "tiptap",
|
||||
noteId: currentNote.current?.id as string
|
||||
};
|
||||
} else if (note.contentId) {
|
||||
const rawContent = await db.content?.raw(note.contentId);
|
||||
if (
|
||||
rawContent &&
|
||||
!isDeleted(rawContent) &&
|
||||
isUnencryptedContent(rawContent)
|
||||
) {
|
||||
currentContent.current = {
|
||||
data: rawContent.data,
|
||||
type: rawContent.type
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const loadNote = useCallback(
|
||||
async (event: { item?: Note; forced?: boolean; newNote?: boolean }) => {
|
||||
state.current.currentlyEditing = true;
|
||||
const editorState = useEditorStore.getState();
|
||||
|
||||
@@ -307,9 +344,9 @@ export const useEditor = (
|
||||
state.current.ready = true;
|
||||
}
|
||||
|
||||
if (item && item.type === "new") {
|
||||
if (event.newNote) {
|
||||
currentNote.current && (await reset());
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
const nextSessionId = makeSessionId(event.item?.id);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
@@ -317,7 +354,10 @@ export const useEditor = (
|
||||
lastContentChangeTime.current = 0;
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
} else {
|
||||
if (!item.forced && currentNote.current?.id === item.id) return;
|
||||
if (!event.item) return;
|
||||
const item = event.item;
|
||||
|
||||
if (!event.forced && currentNote.current?.id === item.id) return;
|
||||
state.current.movedAway = false;
|
||||
state.current.currentlyEditing = true;
|
||||
|
||||
@@ -326,7 +366,7 @@ export const useEditor = (
|
||||
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
|
||||
}
|
||||
|
||||
await loadContent(item as NoteType);
|
||||
await loadContent(item);
|
||||
|
||||
if (
|
||||
currentNote.current?.id === item.id &&
|
||||
@@ -346,16 +386,16 @@ export const useEditor = (
|
||||
overlay(true);
|
||||
}
|
||||
if (!state.current.ready) {
|
||||
currentNote.current = item as NoteType;
|
||||
currentNote.current = item;
|
||||
return;
|
||||
}
|
||||
lastContentChangeTime.current = item.dateEdited;
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
const nextSessionId = makeSessionId(item.id);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
lockedSessionId.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
currentNote.current = item as NoteType;
|
||||
currentNote.current = item;
|
||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
loadingState.current = currentContent.current?.data;
|
||||
@@ -381,7 +421,7 @@ export const useEditor = (
|
||||
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
|
||||
);
|
||||
|
||||
const lockNoteWithVault = useCallback((note: NoteType) => {
|
||||
const lockNoteWithVault = useCallback((note: Note) => {
|
||||
eSendEvent(eClearEditor);
|
||||
openVault({
|
||||
item: note,
|
||||
@@ -394,27 +434,26 @@ export const useEditor = (
|
||||
}, []);
|
||||
|
||||
const onSyncComplete = useCallback(
|
||||
async (data: NoteType | Content) => {
|
||||
async (data: Note | ContentItem) => {
|
||||
if (SettingsService.get().disableRealtimeSync) return;
|
||||
if (!data) return;
|
||||
const noteId = data.type === "tiptap" ? data.noteId : data.id;
|
||||
|
||||
if (!currentNote.current || noteId !== currentNote.current.id) return;
|
||||
const isContentEncrypted = typeof (data as Content)?.data === "object";
|
||||
const note = db.notes?.note(currentNote.current?.id).data as NoteType;
|
||||
const isContentEncrypted =
|
||||
typeof (data as ContentItem)?.data === "object";
|
||||
|
||||
if (lastContentChangeTime.current >= (data as NoteType).dateEdited)
|
||||
return;
|
||||
const note = db.notes?.note(currentNote.current?.id)?.data;
|
||||
|
||||
if (lastContentChangeTime.current >= (data as Note).dateEdited) return;
|
||||
|
||||
lock.current = true;
|
||||
|
||||
if (data.type === "tiptap") {
|
||||
if (data.type === "tiptap" && note) {
|
||||
if (!currentNote.current.locked && isContentEncrypted) {
|
||||
lockNoteWithVault(note);
|
||||
} else if (currentNote.current.locked && isContentEncrypted) {
|
||||
const decryptedContent = (await db.vault?.decryptContent(
|
||||
data
|
||||
)) as Content;
|
||||
} else if (currentNote.current.locked && isEncryptedContent(data)) {
|
||||
const decryptedContent = await db.vault?.decryptContent(data);
|
||||
if (!decryptedContent) {
|
||||
lockNoteWithVault(note);
|
||||
} else {
|
||||
@@ -426,17 +465,23 @@ export const useEditor = (
|
||||
if (_nextContent === currentContent.current?.data) return;
|
||||
lastContentChangeTime.current = note.dateEdited;
|
||||
await postMessage(EditorEvents.updatehtml, _nextContent);
|
||||
currentContent.current = data;
|
||||
if (!isEncryptedContent(data)) {
|
||||
currentContent.current = data as UnencryptedContentItem;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const note = data as NoteType;
|
||||
if (data.type !== "note") return;
|
||||
const note = data;
|
||||
if (note.title !== currentNote.current.title) {
|
||||
postMessage(EditorEvents.title, note.title);
|
||||
}
|
||||
if (note.tags !== currentNote.current.tags) {
|
||||
await commands.setTags(note);
|
||||
}
|
||||
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
|
||||
await commands.setStatus(
|
||||
getFormattedDate(note.dateEdited, "date-time"),
|
||||
"Saved"
|
||||
);
|
||||
}
|
||||
|
||||
lock.current = false;
|
||||
@@ -493,7 +538,7 @@ export const useEditor = (
|
||||
: forSessionId.split("_")[0];
|
||||
|
||||
const noteId = noteIdFromSessionId || currentNote.current?.id;
|
||||
const params = {
|
||||
const params: SavePayload = {
|
||||
title,
|
||||
data: content,
|
||||
type: "tiptap",
|
||||
@@ -539,12 +584,16 @@ export const useEditor = (
|
||||
if (useNoteStore.getState().loading) {
|
||||
const remove = useNoteStore.subscribe((state) => {
|
||||
if (!state.loading && appState.note) {
|
||||
loadNote(appState.note);
|
||||
loadNote({
|
||||
item: appState.note
|
||||
});
|
||||
remove();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loadNote(appState.note);
|
||||
loadNote({
|
||||
item: appState.note
|
||||
});
|
||||
}
|
||||
}
|
||||
clearAppState();
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { AppState, EditorState, useEditorType } from "./types";
|
||||
export const textInput = createRef<TextInput>();
|
||||
export const editorController =
|
||||
@@ -60,8 +59,8 @@ export function randId(prefix: string) {
|
||||
.replace("0.", prefix || "");
|
||||
}
|
||||
|
||||
export function makeSessionId(item?: NoteType) {
|
||||
return item?.id ? item.id + randId("_session_") : randId("session_");
|
||||
export function makeSessionId(id?: string) {
|
||||
return id ? id + randId("_session_") : randId("session_");
|
||||
}
|
||||
|
||||
export async function isEditorLoaded(
|
||||
|
||||
@@ -16,6 +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 { Note, Notebook, Topic } from "@notesnook/core/dist/types";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
@@ -34,12 +35,12 @@ import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { eOnNewTopicAdded } from "../../utils/events";
|
||||
import { NoteType, NotebookType, TopicType } from "../../utils/types";
|
||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
const [notes, setNotes] = useState(
|
||||
groupArray(
|
||||
db.relations?.from(route.params.item, "note") || [],
|
||||
db.relations?.from(route.params.item, "note").resolved(),
|
||||
db.settings.getGroupOptions("notes")
|
||||
)
|
||||
);
|
||||
@@ -80,11 +81,12 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
if (data) params.current = data;
|
||||
params.current.title = params.current.item.title;
|
||||
try {
|
||||
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
|
||||
?.data as NotebookType;
|
||||
const notebook = db.notebooks?.notebook(
|
||||
params?.current?.item?.id
|
||||
)?.data;
|
||||
if (notebook) {
|
||||
params.current.item = notebook;
|
||||
const notes = db.relations?.from(notebook, "note");
|
||||
const notes = db.relations?.from(notebook, "note").resolved();
|
||||
setNotes(
|
||||
groupArray(notes || [], db.settings.getGroupOptions("notes"))
|
||||
);
|
||||
@@ -116,25 +118,26 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
type: "notes",
|
||||
title: params.current.title,
|
||||
get: () => {
|
||||
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
|
||||
?.data as NotebookType;
|
||||
const notebook = db.notebooks?.notebook(
|
||||
params?.current?.item?.id
|
||||
)?.data;
|
||||
if (!notebook) return [];
|
||||
|
||||
const notes = db.relations?.from(notebook, "note") || [];
|
||||
const topicNotes = db.notebooks
|
||||
?.notebook(notebook.id)
|
||||
.topics.all.map((topic: TopicType) => {
|
||||
.notebook(notebook.id)
|
||||
?.topics.all.map((topic: Topic) => {
|
||||
return db.notes?.topicReferences
|
||||
.get(topic.id)
|
||||
.map((id: string) => db.notes?.note(id)?.data);
|
||||
})
|
||||
.flat()
|
||||
.filter(
|
||||
(topicNote: NoteType) =>
|
||||
(topicNote) =>
|
||||
notes.findIndex((note) => note?.id !== topicNote?.id) === -1
|
||||
);
|
||||
) as Note[];
|
||||
|
||||
return [...(notes as []), ...(topicNotes as [])];
|
||||
return [...notes, ...topicNotes];
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -176,7 +179,7 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
);
|
||||
};
|
||||
|
||||
Notebook.navigate = (item: NotebookType, canGoBack: boolean) => {
|
||||
NotebookScreen.navigate = (item: Notebook, canGoBack: boolean) => {
|
||||
if (!item) return;
|
||||
Navigation.navigate<"Notebook">(
|
||||
{
|
||||
@@ -193,4 +196,4 @@ Notebook.navigate = (item: NotebookType, canGoBack: boolean) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Notebook;
|
||||
export default NotebookScreen;
|
||||
|
||||
@@ -17,14 +17,14 @@ 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 { Color } from "@notesnook/core/dist/types";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import React from "react";
|
||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { ColorType } from "../../utils/types";
|
||||
import { getAlias, openEditor, toCamelCase } from "./common";
|
||||
import { openEditor, toCamelCase } from "./common";
|
||||
export const ColoredNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -43,19 +43,17 @@ export const ColoredNotes = ({
|
||||
};
|
||||
|
||||
ColoredNotes.get = (params: NotesScreenParams, grouped = true) => {
|
||||
const notes = db.notes?.colored(params.item.id) || [];
|
||||
const notes = db.relations.from(params.item, "note").resolved();
|
||||
return grouped
|
||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
||||
: notes;
|
||||
};
|
||||
|
||||
ColoredNotes.navigate = (item: ColorType, canGoBack: boolean) => {
|
||||
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||
if (!item) return;
|
||||
const alias = getAlias({ item: item });
|
||||
Navigation.navigate<"ColoredNotes">(
|
||||
{
|
||||
name: "ColoredNotes",
|
||||
alias: toCamelCase(alias as string),
|
||||
title: toCamelCase(item.title),
|
||||
id: item.id,
|
||||
type: "color",
|
||||
@@ -64,7 +62,7 @@ ColoredNotes.navigate = (item: ColorType, canGoBack: boolean) => {
|
||||
{
|
||||
item: item,
|
||||
canGoBack,
|
||||
title: toCamelCase(alias as string)
|
||||
title: toCamelCase(item.title)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,12 +22,11 @@ import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { useTagStore } from "../../stores/use-tag-store";
|
||||
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { TopicType } from "../../utils/types";
|
||||
import { editorController, editorState } from "../editor/tiptap/utils";
|
||||
|
||||
export function toCamelCase(title: string) {
|
||||
@@ -35,12 +34,6 @@ export function toCamelCase(title: string) {
|
||||
return title.slice(0, 1).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
export function getAlias(params: Partial<NotesScreenParams>) {
|
||||
if (!params) return "";
|
||||
const { item } = params;
|
||||
return (item as TopicType)?.alias || item?.title || "";
|
||||
}
|
||||
|
||||
export function openMonographsWebpage() {
|
||||
try {
|
||||
openLinkInBrowser("https://docs.notesnook.com/monographs/");
|
||||
@@ -52,13 +45,13 @@ export function openMonographsWebpage() {
|
||||
export function openEditor() {
|
||||
if (!DDS.isTab) {
|
||||
if (editorController.current?.note) {
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
editorState().currentlyEditing = true;
|
||||
editorState().movedAway = false;
|
||||
}
|
||||
tabBarRef.current?.goToPage(1);
|
||||
} else {
|
||||
eSendEvent(eOnLoadNote, { type: "new" });
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +59,6 @@ type FirstSaveData = {
|
||||
type: string;
|
||||
id: string;
|
||||
notebook?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export const setOnFirstSave = (
|
||||
@@ -74,7 +66,6 @@ export const setOnFirstSave = (
|
||||
type: string;
|
||||
id: string;
|
||||
notebook?: string;
|
||||
color?: string;
|
||||
} | null
|
||||
) => {
|
||||
if (!data) {
|
||||
@@ -82,44 +73,57 @@ export const setOnFirstSave = (
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
|
||||
editorState().onNoteCreated = (noteId) => onNoteCreated(noteId, data);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
export async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
if (!params) return;
|
||||
switch (params.type) {
|
||||
export async function onNoteCreated(noteId: string, data: FirstSaveData) {
|
||||
if (!data) return;
|
||||
switch (data.type) {
|
||||
case "notebook": {
|
||||
await db.relations?.add(
|
||||
{ type: "notebook", id: params.id },
|
||||
{ type: "note", id: id }
|
||||
{ type: "notebook", id: data.id },
|
||||
{ type: "note", id: noteId }
|
||||
);
|
||||
editorState().onNoteCreated = null;
|
||||
useRelationStore.getState().update();
|
||||
break;
|
||||
}
|
||||
case "topic": {
|
||||
if (!params.notebook) break;
|
||||
if (!data.notebook) break;
|
||||
await db.notes?.addToNotebook(
|
||||
{
|
||||
topic: params.id,
|
||||
id: params.notebook
|
||||
topic: data.id,
|
||||
id: data.notebook
|
||||
},
|
||||
id
|
||||
noteId
|
||||
);
|
||||
editorState().onNoteCreated = null;
|
||||
eSendEvent(eOnTopicSheetUpdate);
|
||||
break;
|
||||
}
|
||||
case "tag": {
|
||||
await db.notes?.note(id).tag(params.id);
|
||||
const note = db.notes.note(noteId)?.data;
|
||||
const tag = db.tags.tag(data.id);
|
||||
|
||||
if (tag && note) {
|
||||
await db.relations.add(tag, note);
|
||||
}
|
||||
|
||||
editorState().onNoteCreated = null;
|
||||
useTagStore.getState().setTags();
|
||||
useRelationStore.getState().update();
|
||||
break;
|
||||
}
|
||||
case "color": {
|
||||
await db.notes?.note(id).color(params.color);
|
||||
const note = db.notes.note(noteId)?.data;
|
||||
const color = db.colors.color(data.id);
|
||||
if (note && color) {
|
||||
await db.relations.add(color, note);
|
||||
}
|
||||
editorState().onNoteCreated = null;
|
||||
useMenuStore.getState().setColorNotes();
|
||||
useRelationStore.getState().update();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -17,6 +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 { Color, GroupedItems, Item, Topic } from "@notesnook/core/dist/types";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
@@ -39,10 +40,9 @@ import useNavigationStore, {
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { NoteType, TopicType } from "../../utils/types";
|
||||
import Notebook from "../notebook/index";
|
||||
|
||||
import NotebookScreen from "../notebook/index";
|
||||
import {
|
||||
getAlias,
|
||||
openEditor,
|
||||
openMonographsWebpage,
|
||||
setOnFirstSave,
|
||||
@@ -71,7 +71,7 @@ export const MONOGRAPH_PLACEHOLDER_DATA = {
|
||||
};
|
||||
|
||||
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
||||
get: (params: NotesScreenParams, grouped?: boolean) => NoteType[];
|
||||
get: (params: NotesScreenParams, grouped?: boolean) => GroupedItems<Item>;
|
||||
placeholderData: unknown;
|
||||
onPressFloatingButton: () => void;
|
||||
focusControl?: boolean;
|
||||
@@ -99,16 +99,18 @@ const NotesPage = ({
|
||||
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
||||
>) => {
|
||||
const params = useRef<NotesScreenParams>(route?.params);
|
||||
const [notes, setNotes] = useState<NoteType[]>(get(route.params, true));
|
||||
const [notes, setNotes] = useState(get(route.params, true));
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
const [loadingNotes, setLoadingNotes] = useState(false);
|
||||
const alias = getAlias(params.current);
|
||||
const isMonograph = route.name === "Monographs";
|
||||
|
||||
const notebook =
|
||||
route.name === "TopicNotes" && (params.current.item as TopicType).notebookId
|
||||
? db.notebooks?.notebook((params.current.item as TopicType).notebookId)
|
||||
?.data
|
||||
route.name === "TopicNotes" &&
|
||||
params.current.item.type === "topic" &&
|
||||
params.current.item.notebookId
|
||||
? db.notebooks?.notebook((params.current.item as Topic).notebookId)?.data
|
||||
: null;
|
||||
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
@@ -126,29 +128,32 @@ const NotesPage = ({
|
||||
const prepareSearch = React.useCallback(() => {
|
||||
const { item } = params.current;
|
||||
SearchService.update({
|
||||
placeholder: `Search in ${alias}`,
|
||||
placeholder: `Search in ${item.title}`,
|
||||
type: "notes",
|
||||
title: item.type === "tag" ? "#" + alias : toCamelCase(item.title),
|
||||
title:
|
||||
item.type === "tag"
|
||||
? "#" + item.title
|
||||
: toCamelCase((item as Color).title),
|
||||
get: () => {
|
||||
return get(params.current, false);
|
||||
}
|
||||
});
|
||||
}, [alias, get]);
|
||||
}, [get]);
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
const { item, title } = params.current;
|
||||
const alias = getAlias(params.current);
|
||||
useNavigationStore.getState().update(
|
||||
{
|
||||
name: route.name,
|
||||
title: alias || title,
|
||||
title:
|
||||
route.name === "ColoredNotes" ? toCamelCase(title as string) : title,
|
||||
id: item?.id,
|
||||
type: "notes",
|
||||
notebookId: (item as TopicType).notebookId,
|
||||
alias:
|
||||
route.name === "ColoredNotes" ? toCamelCase(alias as string) : alias,
|
||||
notebookId: item.type === "topic" ? item.notebookId : undefined,
|
||||
color:
|
||||
route.name === "ColoredNotes" ? item.title?.toLowerCase() : undefined
|
||||
item.type === "color" && route.name === "ColoredNotes"
|
||||
? item.title?.toLowerCase()
|
||||
: undefined
|
||||
},
|
||||
params.current.canGoBack,
|
||||
rightButtons && rightButtons(params.current)
|
||||
@@ -160,8 +165,7 @@ const NotesPage = ({
|
||||
setOnFirstSave({
|
||||
type: getItemType(route.name),
|
||||
id: item.id,
|
||||
color: item.title,
|
||||
notebook: (item as TopicType).notebookId
|
||||
notebook: item.type === "topic" ? item.notebookId : undefined
|
||||
});
|
||||
}, [
|
||||
isMonograph,
|
||||
@@ -175,11 +179,13 @@ const NotesPage = ({
|
||||
(data?: NotesScreenParams) => {
|
||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||
if (data) params.current = data;
|
||||
params.current.title = params.current.title || params.current.item.title;
|
||||
params.current.title =
|
||||
params.current.title ||
|
||||
(params.current.item as Item & { title: string }).title;
|
||||
const { item } = params.current;
|
||||
try {
|
||||
if (isNew) setLoadingNotes(true);
|
||||
const notes = get(params.current, true) as NoteType[];
|
||||
const notes = get(params.current, true);
|
||||
if (
|
||||
((item.type === "tag" || item.type === "color") &&
|
||||
(!notes || notes.length === 0)) ||
|
||||
@@ -215,7 +221,7 @@ const NotesPage = ({
|
||||
<DelayLayout
|
||||
color={
|
||||
route.name === "ColoredNotes"
|
||||
? params.current?.item.title.toLowerCase()
|
||||
? (params.current?.item as Color).title.toLowerCase()
|
||||
: undefined
|
||||
}
|
||||
wait={loading || loadingNotes}
|
||||
@@ -233,12 +239,10 @@ const NotesPage = ({
|
||||
>
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Navigation.navigate(
|
||||
{
|
||||
name: "Notebooks"
|
||||
},
|
||||
{}
|
||||
);
|
||||
Navigation.navigate({
|
||||
name: "Notebooks",
|
||||
title: "Notebooks"
|
||||
});
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
@@ -253,7 +257,7 @@ const NotesPage = ({
|
||||
/>
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Notebook.navigate(notebook, true);
|
||||
NotebookScreen.navigate(notebook, true);
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
@@ -273,7 +277,7 @@ const NotesPage = ({
|
||||
heading: params.current.title,
|
||||
color:
|
||||
route.name === "ColoredNotes"
|
||||
? params.current?.item.title.toLowerCase()
|
||||
? (params.current?.item as Color).title.toLowerCase()
|
||||
: null
|
||||
}}
|
||||
placeholderData={placeholderData}
|
||||
|
||||
@@ -42,14 +42,14 @@ export const Monographs = ({
|
||||
);
|
||||
};
|
||||
|
||||
Monographs.get = (params: NotesScreenParams, grouped = true) => {
|
||||
Monographs.get = (params?: NotesScreenParams, grouped = true) => {
|
||||
const notes = db.monographs?.all || [];
|
||||
return grouped
|
||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
||||
: notes;
|
||||
};
|
||||
|
||||
Monographs.navigate = (item: MonographType, canGoBack: boolean) => {
|
||||
Monographs.navigate = (item?: MonographType, canGoBack?: boolean) => {
|
||||
Navigation.navigate<"Monographs">(
|
||||
{
|
||||
name: "Monographs",
|
||||
@@ -57,7 +57,7 @@ Monographs.navigate = (item: MonographType, canGoBack: boolean) => {
|
||||
},
|
||||
{
|
||||
item: { type: "monograph" } as any,
|
||||
canGoBack,
|
||||
canGoBack: canGoBack as boolean,
|
||||
title: "Monographs"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,8 +23,8 @@ import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { NoteType, TagType } from "../../utils/types";
|
||||
import { getAlias, openEditor } from "./common";
|
||||
import { openEditor } from "./common";
|
||||
import { Tag } from "@notesnook/core/dist/types";
|
||||
export const TaggedNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -43,19 +43,17 @@ export const TaggedNotes = ({
|
||||
};
|
||||
|
||||
TaggedNotes.get = (params: NotesScreenParams, grouped = true) => {
|
||||
const notes = db.notes?.tagged((params.item as unknown as NoteType).id) || [];
|
||||
const notes = db.relations.from(params.item, "note").resolved();
|
||||
return grouped
|
||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
||||
: notes;
|
||||
};
|
||||
|
||||
TaggedNotes.navigate = (item: TagType, canGoBack: boolean) => {
|
||||
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||
if (!item) return;
|
||||
const alias = getAlias({ item: item });
|
||||
Navigation.navigate<"TaggedNotes">(
|
||||
{
|
||||
name: "TaggedNotes",
|
||||
alias: alias,
|
||||
title: item.title,
|
||||
id: item.id,
|
||||
type: "tag"
|
||||
@@ -63,7 +61,7 @@ TaggedNotes.navigate = (item: TagType, canGoBack: boolean) => {
|
||||
{
|
||||
item: item,
|
||||
canGoBack,
|
||||
title: alias
|
||||
title: item.title
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +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 { Topic } from "@notesnook/core/dist/types";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import React from "react";
|
||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
@@ -26,8 +27,8 @@ import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { eOpenAddTopicDialog } from "../../utils/events";
|
||||
import { NotebookType, TopicType } from "../../utils/types";
|
||||
import { openEditor } from "./common";
|
||||
|
||||
const headerRightButtons = (params: NotesScreenParams) => [
|
||||
{
|
||||
title: "Edit topic",
|
||||
@@ -45,10 +46,10 @@ const headerRightButtons = (params: NotesScreenParams) => [
|
||||
onPress: () => {
|
||||
const { item } = params;
|
||||
if (item?.type !== "topic") return;
|
||||
MoveNotes.present(
|
||||
db.notebooks?.notebook(item.notebookId).data as NotebookType,
|
||||
item
|
||||
);
|
||||
const notebook = db.notebooks?.notebook(item.notebookId);
|
||||
if (notebook) {
|
||||
MoveNotes.present(notebook.data, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -74,10 +75,10 @@ export const TopicNotes = ({
|
||||
};
|
||||
|
||||
TopicNotes.get = (params: NotesScreenParams, grouped = true) => {
|
||||
const { id, notebookId } = params.item as TopicType;
|
||||
const { id, notebookId } = params.item as Topic;
|
||||
const topic = db.notebooks?.notebook(notebookId)?.topics.topic(id);
|
||||
if (!topic) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
const notes = topic?.all || [];
|
||||
return grouped
|
||||
@@ -85,7 +86,7 @@ TopicNotes.get = (params: NotesScreenParams, grouped = true) => {
|
||||
: notes;
|
||||
};
|
||||
|
||||
TopicNotes.navigate = (item: TopicType, canGoBack: boolean) => {
|
||||
TopicNotes.navigate = (item: Topic, canGoBack: boolean) => {
|
||||
if (!item) return;
|
||||
Navigation.navigate<"TopicNotes">(
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { TextInput } from "react-native-gesture-handler";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import { useSearchStore } from "../../stores/use-search-store";
|
||||
@@ -70,8 +70,8 @@ export const SearchBar = () => {
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
ToastEvent.show({
|
||||
heading: "Error occurred while searching",
|
||||
ToastManager.show({
|
||||
heading: "Error occured while searching",
|
||||
message: e.message,
|
||||
type: "error"
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ import useTimer from "../../hooks/use-timer";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
@@ -198,18 +198,18 @@ export const MFASetup = ({
|
||||
if (!method) return;
|
||||
setEnabling(true);
|
||||
if (recovery) {
|
||||
await db.mfa?.enableFallback(method.id, code.current);
|
||||
await db.mfa.enableFallback(method.id, code.current);
|
||||
} else {
|
||||
await db.mfa?.enable(method.id, code.current);
|
||||
await db.mfa.enable(method.id, code.current);
|
||||
}
|
||||
|
||||
const user = await db.user?.fetchUser();
|
||||
const user = await db.user.fetchUser();
|
||||
useUserStore.getState().setUser(user);
|
||||
onSuccess && onSuccess(method);
|
||||
setEnabling(false);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
ToastEvent.error(error, "Error submitting 2fa code");
|
||||
ToastManager.error(error, "Error submitting 2fa code");
|
||||
setEnabling(false);
|
||||
}
|
||||
};
|
||||
@@ -225,7 +225,7 @@ export const MFASetup = ({
|
||||
);
|
||||
}
|
||||
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Code copied!",
|
||||
type: "success",
|
||||
context: "local"
|
||||
@@ -239,7 +239,7 @@ export const MFASetup = ({
|
||||
if (method.id === "sms" && !phoneNumber.current)
|
||||
throw new Error("Phone number not entered");
|
||||
setSending(true);
|
||||
await db.mfa?.setup(method?.id, phoneNumber.current);
|
||||
await db.mfa.setup(method?.id, phoneNumber.current);
|
||||
|
||||
if (method.id === "sms") {
|
||||
setId(method.id + phoneNumber.current);
|
||||
@@ -250,7 +250,7 @@ export const MFASetup = ({
|
||||
method.id === "sms" ? method.id + phoneNumber.current : method.id
|
||||
);
|
||||
setSending(false);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: `2FA code sent via ${method.id}.`,
|
||||
type: "success",
|
||||
context: "local"
|
||||
@@ -258,7 +258,7 @@ export const MFASetup = ({
|
||||
} catch (e) {
|
||||
setSending(false);
|
||||
const error = e as Error;
|
||||
ToastEvent.error(error, "Error sending 2FA code");
|
||||
ToastManager.error(error, "Error sending 2FA code");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -408,12 +408,12 @@ export const MFARecoveryCodes = ({
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const codes = await db.mfa?.codes();
|
||||
const codes = await db.mfa.codes();
|
||||
if (codes) setCodes(codes);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
ToastEvent.error(error, "Error getting codes", "local");
|
||||
ToastManager.error(error, "Error getting codes", "local");
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
@@ -488,7 +488,7 @@ export const MFARecoveryCodes = ({
|
||||
onPress={() => {
|
||||
const codeString = codes.join("\n");
|
||||
Clipboard.setString(codeString);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery codes copied!",
|
||||
type: "success",
|
||||
context: "global"
|
||||
@@ -528,7 +528,7 @@ export const MFARecoveryCodes = ({
|
||||
path = path + fileName;
|
||||
}
|
||||
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Recovery codes saved to text file",
|
||||
type: "success",
|
||||
context: "local"
|
||||
|
||||
@@ -30,7 +30,7 @@ import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import BiometicService from "../../services/biometrics";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
presentSheet
|
||||
} from "../../services/event-manager";
|
||||
@@ -212,7 +212,7 @@ const AppLock = ({ route }) => {
|
||||
!useUserStore.getState().user &&
|
||||
item.value !== modes[0].value
|
||||
) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Biometrics not enrolled",
|
||||
type: "error",
|
||||
message:
|
||||
|
||||
@@ -30,7 +30,7 @@ import { IconButton } from "../../components/ui/icon-button";
|
||||
import { Notice } from "../../components/ui/notice";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import useTimer from "../../hooks/use-timer";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { hexToRGBA } from "../../utils/colors";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
@@ -88,7 +88,7 @@ export default function DebugLogs() {
|
||||
activeOpacity={1}
|
||||
onLongPress={() => {
|
||||
Clipboard.setString(format(item));
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Debug log copied!",
|
||||
context: "global",
|
||||
type: "success"
|
||||
@@ -152,7 +152,7 @@ export default function DebugLogs() {
|
||||
}
|
||||
|
||||
if (path) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Debug logs downloaded",
|
||||
context: "global",
|
||||
type: "success"
|
||||
@@ -171,7 +171,7 @@ export default function DebugLogs() {
|
||||
.join("\n");
|
||||
if (!data) return;
|
||||
Clipboard.setString(data);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Debug log copied!",
|
||||
context: "global",
|
||||
type: "success"
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import { db } from "../../common/database";
|
||||
import { sleep } from "../../utils/time";
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function verifyUser(
|
||||
await onsuccess();
|
||||
});
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message: "The account password you entered is incorrect",
|
||||
type: "error",
|
||||
@@ -58,7 +58,7 @@ export async function verifyUser(
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Failed to verify",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "../../../common/database";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { MenuItemsList } from "../../../utils/menu-items";
|
||||
@@ -48,7 +48,7 @@ export const HomePicker = createSettingsPicker({
|
||||
getValue: () => useSettingStore.getState().settings.homepage,
|
||||
updateValue: (item) => {
|
||||
SettingsService.set({ homepage: item.name });
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Homepage set to " + item.name,
|
||||
message: "Restart the app for changes to take effect.",
|
||||
type: "success"
|
||||
|
||||
@@ -38,7 +38,7 @@ import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
|
||||
import BackupService from "../../services/backup";
|
||||
import BiometicService from "../../services/biometrics";
|
||||
import {
|
||||
ToastEvent,
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
openVault,
|
||||
presentSheet
|
||||
@@ -307,7 +307,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
}, 2000);
|
||||
}, 300);
|
||||
} catch (e) {
|
||||
ToastEvent.error(e as Error, "Error logging out");
|
||||
ToastManager.error(e as Error, "Error logging out");
|
||||
eSendEvent("settings-loading", false);
|
||||
}
|
||||
}
|
||||
@@ -342,7 +342,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
introCompleted: true
|
||||
});
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"The account password you entered is incorrect",
|
||||
@@ -355,7 +355,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
} catch (e) {
|
||||
eSendEvent("settings-loading", false);
|
||||
console.log(e);
|
||||
ToastEvent.error(
|
||||
ToastManager.error(
|
||||
e as Error,
|
||||
"Failed to delete account",
|
||||
"global"
|
||||
@@ -553,7 +553,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
property: "doubleSpacedLines",
|
||||
icon: "format-line-spacing",
|
||||
onChange: () => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Line spacing changed",
|
||||
type: "success"
|
||||
});
|
||||
@@ -622,7 +622,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
);
|
||||
useUserStore.getState().setUser(await db.user?.fetchUser());
|
||||
} catch (e) {
|
||||
ToastEvent.error(e as Error);
|
||||
ToastManager.error(e as Error);
|
||||
}
|
||||
},
|
||||
getter: (current: any) => current?.marketingConsent,
|
||||
@@ -824,7 +824,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
console.error(e);
|
||||
} finally {
|
||||
if (!dir) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "No directory selected",
|
||||
type: "error"
|
||||
});
|
||||
@@ -850,7 +850,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
console.error(e);
|
||||
} finally {
|
||||
if (!dir) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "No directory selected",
|
||||
type: "error"
|
||||
});
|
||||
@@ -869,7 +869,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
const user = useUserStore.getState().user;
|
||||
const settings = SettingsService.get();
|
||||
if (!user) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Login required to enable encryption",
|
||||
type: "error",
|
||||
func: () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { usePricing } from "../../hooks/use-pricing";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
@@ -68,7 +68,7 @@ export const Subscription = () => {
|
||||
|
||||
if (hasCancelledPremium && Platform.OS === "android") {
|
||||
if (user.subscription?.provider === 3) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Subscribed on web",
|
||||
message: "Open your web browser to manage your subscription.",
|
||||
type: "success"
|
||||
|
||||
@@ -47,7 +47,7 @@ import { Button } from "../../components/ui/button";
|
||||
import Input from "../../components/ui/input";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { ToastEvent, presentSheet } from "../../services/event-manager";
|
||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
@@ -405,7 +405,7 @@ function ThemeSelector() {
|
||||
const json = await response.json();
|
||||
const result = validateTheme(json);
|
||||
if (result.error) {
|
||||
ToastEvent.error(new Error(result.error));
|
||||
ToastManager.error(new Error(result.error));
|
||||
return;
|
||||
}
|
||||
select(json, true);
|
||||
@@ -533,7 +533,7 @@ const ThemeSetter = ({
|
||||
theme.colorScheme === "dark"
|
||||
? useThemeStore.getState().setDarkTheme(fullTheme)
|
||||
: useThemeStore.getState().setLightTheme(fullTheme);
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: `${theme.name} applied successfully`,
|
||||
type: "success",
|
||||
context: "global"
|
||||
|
||||
@@ -24,7 +24,7 @@ import DelayLayout from "../../components/delay-layout";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import List from "../../components/list";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
@@ -49,7 +49,7 @@ const onPressFloatingButton = () => {
|
||||
await db.trash?.clear();
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().clearSelection();
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Trash cleared",
|
||||
message:
|
||||
"All notes and notebooks in the trash have been removed permanently.",
|
||||
|
||||
@@ -27,7 +27,7 @@ import storage from "../common/database/storage";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { eCloseSheet } from "../utils/events";
|
||||
import { sleep } from "../utils/time";
|
||||
import { ToastEvent, eSendEvent, presentSheet } from "./event-manager";
|
||||
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
|
||||
import { zip } from "react-native-zip-archive";
|
||||
|
||||
@@ -25,7 +25,7 @@ import * as Keychain from "react-native-keychain";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import Storage from "../common/database/storage";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { ShowToastEvent, ToastEvent } from "./event-manager";
|
||||
import { ToastOptions, ToastManager } from "./event-manager";
|
||||
|
||||
const KeychainConfig = Platform.select({
|
||||
ios: {
|
||||
@@ -93,7 +93,7 @@ async function getCredentials(title?: string, description?: string) {
|
||||
const e = error as { name: string };
|
||||
useSettingStore.getState().setRequestBiometrics(false);
|
||||
FingerprintScanner.release();
|
||||
let message: ShowToastEvent = {
|
||||
let message: ToastOptions = {
|
||||
heading: "Authentication with biometrics failed.",
|
||||
message: 'Tap "Biometric Unlock" to try again.',
|
||||
type: "error",
|
||||
@@ -115,7 +115,7 @@ async function getCredentials(title?: string, description?: string) {
|
||||
};
|
||||
}
|
||||
|
||||
setTimeout(() => ToastEvent.show(message), 1000);
|
||||
setTimeout(() => ToastManager.show(message), 1000);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -141,14 +141,14 @@ async function validateUser(title: string, description?: string) {
|
||||
const e = error as { name: string };
|
||||
FingerprintScanner.release();
|
||||
if (e.name === "DeviceLocked") {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Biometrics authentication failed.",
|
||||
message: "Wait 30 seconds to try again.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Authentication failed.",
|
||||
message: "Tap to try again.",
|
||||
type: "error",
|
||||
|
||||
@@ -126,17 +126,17 @@ export function hideSheet() {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
|
||||
export type ShowToastEvent = {
|
||||
export type ToastOptions = {
|
||||
heading?: string;
|
||||
message?: string;
|
||||
context?: "global" | "local";
|
||||
type?: "error" | "success";
|
||||
type?: "error" | "success" | "info";
|
||||
duration?: number;
|
||||
func?: () => void;
|
||||
actionText?: string;
|
||||
};
|
||||
|
||||
export const ToastEvent = {
|
||||
export const ToastManager = {
|
||||
show: ({
|
||||
heading,
|
||||
message,
|
||||
@@ -144,7 +144,7 @@ export const ToastEvent = {
|
||||
context = "global",
|
||||
func,
|
||||
actionText
|
||||
}: ShowToastEvent) => {
|
||||
}: ToastOptions) => {
|
||||
if (Config.isTesting) return;
|
||||
eSendEvent(eShowToast, {
|
||||
heading: heading,
|
||||
@@ -158,7 +158,7 @@ export const ToastEvent = {
|
||||
},
|
||||
hide: () => eSendEvent(eHideToast),
|
||||
error: (e: Error, title?: string, context?: "global" | "local") => {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: title,
|
||||
message: e?.message || "",
|
||||
type: "error",
|
||||
@@ -167,7 +167,7 @@ export const ToastEvent = {
|
||||
duration: 6000,
|
||||
func: () => {
|
||||
Clipboard.setString(e?.stack || "");
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Logs copied!",
|
||||
type: "success",
|
||||
context: "global",
|
||||
@@ -177,7 +177,3 @@ export const ToastEvent = {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
@@ -101,8 +101,10 @@ async function save(path, data, fileName, extension) {
|
||||
return uri || path;
|
||||
}
|
||||
|
||||
async function makeHtml(note, content) {
|
||||
let html = await db.notes.note(note.id).export("html", content);
|
||||
async function makeHtml(note) {
|
||||
let html = await db.notes.export(note.id, {
|
||||
format: "html"
|
||||
});
|
||||
html = decode(html, {
|
||||
level: EntityLevel.HTML
|
||||
});
|
||||
@@ -122,7 +124,9 @@ async function exportAs(type, note, bulk, content) {
|
||||
}
|
||||
break;
|
||||
case "md":
|
||||
data = await db.notes.note(note.id).export("md", content);
|
||||
data = await db.notes.export(note.id, {
|
||||
format: "md"
|
||||
});
|
||||
break;
|
||||
case "md-frontmatter":
|
||||
data = await db.notes
|
||||
|
||||
@@ -16,6 +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 React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { verifyUser } from "../screens/settings/functions";
|
||||
|
||||
@@ -125,13 +125,18 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
|
||||
}
|
||||
|
||||
function navigate<T extends RouteName>(
|
||||
screen: CurrentScreen,
|
||||
params: RouteParams[T]
|
||||
screen: Omit<Partial<CurrentScreen>, "name"> & {
|
||||
name: keyof RouteParams;
|
||||
},
|
||||
params?: RouteParams[T]
|
||||
) {
|
||||
useNavigationStore.getState().update(screen, !!params?.canGoBack);
|
||||
if (screen.name === "Notebook") routeUpdateFunctions["Notebook"](params);
|
||||
if (screen.name.endsWith("Notes") && screen.name !== "Notes")
|
||||
routeUpdateFunctions[screen.name]?.(params);
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.update(screen as CurrentScreen, !!params?.canGoBack);
|
||||
if (screen.name === "Notebook")
|
||||
routeUpdateFunctions["Notebook"](params || {});
|
||||
if (screen.name?.endsWith("Notes") && screen.name !== "Notes")
|
||||
routeUpdateFunctions[screen.name]?.(params || {});
|
||||
//@ts-ignore Not sure how to fix this for now ignore it.
|
||||
rootNavigatorRef.current?.navigate<RouteName>(screen.name, params);
|
||||
}
|
||||
|
||||
@@ -48,23 +48,24 @@ import { presentDialog } from "../components/dialog/functions";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import { encodeNonAsciiHTML } from "entities";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { Reminder } from "@notesnook/core/dist/types";
|
||||
|
||||
export type Reminder = {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
priority: "silent" | "vibrate" | "urgent";
|
||||
date: number;
|
||||
mode: "repeat" | "once" | "permanent";
|
||||
recurringMode?: "week" | "month" | "day";
|
||||
selectedDays?: number[];
|
||||
dateCreated: number;
|
||||
dateModified: number;
|
||||
localOnly?: boolean;
|
||||
snoozeUntil?: number;
|
||||
disabled?: boolean;
|
||||
};
|
||||
// export type Reminder = {
|
||||
// id: string;
|
||||
// type: string;
|
||||
// title: string;
|
||||
// description?: string;
|
||||
// priority: "silent" | "vibrate" | "urgent";
|
||||
// date: number;
|
||||
// mode: "repeat" | "once" | "permanent";
|
||||
// recurringMode?: "week" | "month" | "day";
|
||||
// selectedDays?: number[];
|
||||
// dateCreated: number;
|
||||
// dateModified: number;
|
||||
// localOnly?: boolean;
|
||||
// snoozeUntil?: number;
|
||||
// disabled?: boolean;
|
||||
// };
|
||||
|
||||
let pinned: DisplayedNotification[] = [];
|
||||
|
||||
@@ -122,11 +123,15 @@ async function initDatabase(notes = true) {
|
||||
const onEvent = async ({ type, detail }: Event) => {
|
||||
const { notification, pressAction, input } = detail;
|
||||
if (type === EventType.DELIVERED && Platform.OS === "android") {
|
||||
const reminder = db.reminders?.reminder(notification?.id?.split("_")[0]);
|
||||
if (reminder && reminder.recurringMode === "month") {
|
||||
await initDatabase();
|
||||
await scheduleNotification(reminder);
|
||||
if (notification?.id) {
|
||||
const reminder = db.reminders?.reminder(notification?.id?.split("_")[0]);
|
||||
|
||||
if (reminder && reminder.recurringMode === "month") {
|
||||
await initDatabase();
|
||||
await scheduleNotification(reminder);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (type === EventType.PRESS) {
|
||||
@@ -134,7 +139,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
if (notification?.data?.type === "quickNote") return;
|
||||
MMKV.removeItem("appState");
|
||||
await initDatabase();
|
||||
if (notification?.data?.type === "reminder") {
|
||||
if (notification?.data?.type === "reminder" && notification?.id) {
|
||||
const reminder = db.reminders?.reminder(notification.id?.split("_")[0]);
|
||||
if (!reminder) return;
|
||||
await sleep(1000);
|
||||
@@ -166,9 +171,12 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
switch (pressAction?.id) {
|
||||
case "REMINDER_SNOOZE": {
|
||||
await initDatabase();
|
||||
if (!notification?.id) break;
|
||||
const reminder = db.reminders?.reminder(
|
||||
notification?.id?.split("_")[0]
|
||||
);
|
||||
if (!reminder) break;
|
||||
|
||||
const reminderTime = parseInt(
|
||||
SettingsService.get().defaultSnoozeTime || "5"
|
||||
);
|
||||
@@ -185,6 +193,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
}
|
||||
case "REMINDER_DISABLE": {
|
||||
await initDatabase();
|
||||
if (!notification?.id) break;
|
||||
const reminder = db.reminders?.reminder(
|
||||
notification?.id?.split("_")[0]
|
||||
);
|
||||
@@ -192,6 +201,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
...reminder,
|
||||
disabled: true
|
||||
});
|
||||
if (!reminder?.id) break;
|
||||
await Notifications.scheduleNotification(
|
||||
db.reminders?.reminder(reminder?.id)
|
||||
);
|
||||
@@ -201,6 +211,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
}
|
||||
case "UNPIN": {
|
||||
await initDatabase();
|
||||
if (!notification?.id) break;
|
||||
remove(notification?.id as string);
|
||||
const reminder = db.reminders?.reminder(
|
||||
notification?.id?.split("_")[0]
|
||||
@@ -439,7 +450,9 @@ function loadNote(id: string, jump: boolean) {
|
||||
}
|
||||
eSendEvent("loadingNote", note);
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOnLoadNote, note);
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!jump && !DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
@@ -918,6 +931,9 @@ async function pinNote(id: string) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
const Events = {
|
||||
onUpdate: "onUpdate"
|
||||
};
|
||||
|
||||
const Notifications = {
|
||||
init,
|
||||
@@ -936,7 +952,8 @@ const Notifications = {
|
||||
setupReminders,
|
||||
getChannelId,
|
||||
isNotePinned,
|
||||
pinNote
|
||||
pinNote,
|
||||
Events
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
eOpenTrialEndingDialog,
|
||||
eShowGetPremium
|
||||
} from "../utils/events";
|
||||
import { eSendEvent, presentSheet, ToastEvent } from "./event-manager";
|
||||
import { eSendEvent, presentSheet, ToastManager } from "./event-manager";
|
||||
|
||||
import SettingsService from "./settings";
|
||||
let premiumStatus = 0;
|
||||
@@ -195,7 +195,7 @@ const showVerifyEmailDialog = () => {
|
||||
lastVerificationEmailTime &&
|
||||
Date.now() - lastVerificationEmailTime < 60000 * 2
|
||||
) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Please wait before requesting another email",
|
||||
type: "error",
|
||||
context: "local"
|
||||
@@ -208,7 +208,7 @@ const showVerifyEmailDialog = () => {
|
||||
lastVerificationEmailTime: Date.now()
|
||||
});
|
||||
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Verification email sent!",
|
||||
message:
|
||||
"We have sent you an email confirmation link. Please check your email inbox to verify your account. If you cannot find the email, check your spam folder.",
|
||||
@@ -216,7 +216,7 @@ const showVerifyEmailDialog = () => {
|
||||
context: "local"
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
ToastManager.show({
|
||||
heading: "Could not send email",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
|
||||
@@ -23,7 +23,7 @@ import { DatabaseLogger } from "../common/database/index";
|
||||
import { initAfterSync } from "../stores/index";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import BackgroundSync from "./background-sync";
|
||||
import { ToastEvent } from "./event-manager";
|
||||
import { ToastManager } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
|
||||
export const ignoredMessages = [
|
||||
@@ -100,7 +100,10 @@ const run = async (
|
||||
status.isConnected &&
|
||||
status.isInternetReachable
|
||||
) {
|
||||
ToastEvent.error(e, "Sync failed", context);
|
||||
userstore.setSyncing(false, SyncStatus.Failed);
|
||||
if (status.isConnected && status.isInternetReachable) {
|
||||
ToastManager.error(e, "Sync failed", context);
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseLogger.error(e, "[Client] Failed to sync");
|
||||
|
||||
@@ -37,7 +37,6 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
||||
setCurrentlyEditingNote: (note) => set({ currentEditingNote: note }),
|
||||
sessionId: null,
|
||||
setSessionId: (sessionId) => {
|
||||
// tiny.call(EditorWebView, `sessionId="${sessionId}";`);
|
||||
set({ sessionId });
|
||||
},
|
||||
searchReplace: false,
|
||||
|
||||
@@ -17,14 +17,14 @@ 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 { GroupedItems, Note } from "@notesnook/core/dist/types";
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
import { NoteType } from "../utils/types";
|
||||
|
||||
export interface FavoriteStore extends State {
|
||||
favorites: NoteType[];
|
||||
setFavorites: (items?: NoteType[]) => void;
|
||||
favorites: GroupedItems<Note>;
|
||||
setFavorites: (items?: Note[]) => void;
|
||||
clearFavorites: () => void;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useFavoriteStore = create<FavoriteStore>((set, get) => ({
|
||||
if (!items) {
|
||||
set({
|
||||
favorites: groupArray(
|
||||
db?.notes?.favorites || [],
|
||||
db.notes.favorites || [],
|
||||
db.settings.getGroupOptions("favorites")
|
||||
)
|
||||
});
|
||||
|
||||
@@ -17,13 +17,13 @@ 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 { Color } from "@notesnook/core/dist/types";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
import { ColorType } from "../utils/types";
|
||||
|
||||
export interface MenuStore extends State {
|
||||
menuPins: [];
|
||||
colorNotes: ColorType[];
|
||||
colorNotes: Color[];
|
||||
setMenuPins: () => void;
|
||||
setColorNotes: () => void;
|
||||
clearAll: () => void;
|
||||
@@ -45,7 +45,6 @@ export const useMenuStore = create<MenuStore>((set) => ({
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
setColorNotes: () =>
|
||||
set({ colorNotes: (db.colors?.all as ColorType[]) || [] }),
|
||||
setColorNotes: () => set({ colorNotes: db.colors?.all || [] }),
|
||||
clearAll: () => set({ menuPins: [], colorNotes: [] })
|
||||
}));
|
||||
|
||||
@@ -17,6 +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 { User } from "@notesnook/core/dist/api/user-manager";
|
||||
import { Platform } from "react-native";
|
||||
import { getVersion } from "react-native-device-info";
|
||||
import create, { State } from "zustand";
|
||||
|
||||
@@ -17,39 +17,41 @@ 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 {
|
||||
Color,
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
Tag,
|
||||
Topic,
|
||||
TrashItem
|
||||
} from "@notesnook/core/dist/types";
|
||||
import create, { State } from "zustand";
|
||||
import { ColorValues } from "../utils/colors";
|
||||
import {
|
||||
ColorType,
|
||||
MonographType,
|
||||
NotebookType,
|
||||
TagType,
|
||||
TopicType
|
||||
} from "../utils/types";
|
||||
|
||||
export type GenericRouteParam = { [name: string]: unknown };
|
||||
|
||||
export type NotebookScreenParams = {
|
||||
item: NotebookType;
|
||||
item: Notebook;
|
||||
title: string;
|
||||
canGoBack: boolean;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
export type NotesScreenParams = {
|
||||
item: TopicType | TagType | ColorType | MonographType;
|
||||
item: Note | Notebook | Topic | Tag | Color | TrashItem | Reminder;
|
||||
title: string;
|
||||
canGoBack: boolean;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
export type AppLockRouteParams = {
|
||||
welcome: boolean;
|
||||
canGoBack: boolean;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
export type AuthParams = {
|
||||
mode: number;
|
||||
title: string;
|
||||
canGoBack: boolean;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
export type RouteParams = {
|
||||
@@ -76,11 +78,10 @@ export type RouteName = keyof RouteParams;
|
||||
|
||||
export type CurrentScreen = {
|
||||
name: RouteName;
|
||||
id?: string;
|
||||
id: string;
|
||||
title?: string;
|
||||
type?: string;
|
||||
color?: string | null;
|
||||
alias?: string;
|
||||
notebookId?: string;
|
||||
beta?: boolean;
|
||||
};
|
||||
@@ -92,10 +93,12 @@ export type HeaderRightButton = {
|
||||
|
||||
interface NavigationStore extends State {
|
||||
currentScreen: CurrentScreen;
|
||||
currentScreenRaw: CurrentScreen;
|
||||
canGoBack: boolean | undefined;
|
||||
currentScreenRaw: Partial<CurrentScreen>;
|
||||
canGoBack?: boolean;
|
||||
update: (
|
||||
currentScreen: CurrentScreen,
|
||||
currentScreen: Omit<Partial<CurrentScreen>, "name"> & {
|
||||
name: keyof RouteParams;
|
||||
},
|
||||
canGoBack?: boolean,
|
||||
headerRightButtons?: HeaderRightButton[]
|
||||
) => void;
|
||||
@@ -127,7 +130,7 @@ const useNavigationStore = create<NavigationStore>((set, get) => ({
|
||||
name: currentScreen.name,
|
||||
id:
|
||||
currentScreen.id || currentScreen.name.toLowerCase() + "_navigation",
|
||||
title: currentScreen.alias || currentScreen.title || currentScreen.name,
|
||||
title: currentScreen.title || currentScreen.name,
|
||||
type: currentScreen.type,
|
||||
color: color,
|
||||
notebookId: currentScreen.notebookId,
|
||||
|
||||
@@ -20,10 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
import { NotebookType } from "../utils/types";
|
||||
import { GroupedItems, Notebook } from "@notesnook/core/dist/types";
|
||||
|
||||
export interface NotebookStore extends State {
|
||||
notebooks: NotebookType[];
|
||||
setNotebooks: (items?: NotebookType[]) => void;
|
||||
notebooks: GroupedItems<Notebook>;
|
||||
setNotebooks: (items?: Notebook[]) => void;
|
||||
clearNotebooks: () => void;
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ export const useNotebookStore = create<NotebookStore>((set, get) => ({
|
||||
if (!items) {
|
||||
set({
|
||||
notebooks: groupArray(
|
||||
(db?.notebooks?.all as NotebookType[]) || [],
|
||||
db.notebooks.all || [],
|
||||
db.settings.getGroupOptions("notebooks")
|
||||
)
|
||||
});
|
||||
|
||||
@@ -20,13 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
import { NoteType } from "../utils/types";
|
||||
import { GroupedItems, Note } from "@notesnook/core/dist/types";
|
||||
|
||||
export interface NoteStore extends State {
|
||||
notes: NoteType[];
|
||||
notes: GroupedItems<Note>;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setNotes: (items?: NoteType[]) => void;
|
||||
setNotes: (items?: Note[]) => void;
|
||||
clearNotes: () => void;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useNoteStore = create<NoteStore>((set, get) => ({
|
||||
if (!items) {
|
||||
set({
|
||||
notes: groupArray(
|
||||
(db.notes?.all as NoteType[]) || [],
|
||||
db.notes.all || [],
|
||||
db.settings.getGroupOptions("home")
|
||||
)
|
||||
});
|
||||
|
||||
@@ -20,10 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { groupReminders } from "@notesnook/core/dist/utils/grouping";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
import { Reminder } from "../services/notifications";
|
||||
import { GroupedItems, Reminder } from "@notesnook/core/dist/types";
|
||||
|
||||
export interface ReminderStore extends State {
|
||||
reminders: (Reminder | { title: string; type: "header" })[];
|
||||
reminders: GroupedItems<Reminder>;
|
||||
setReminders: (items?: Reminder[]) => void;
|
||||
cleareReminders: () => void;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user