mobile: migrate to typescript

This commit is contained in:
ammarahm-ed
2023-08-29 20:42:45 +05:00
committed by Abdullah Atta
parent 9ef40c01ce
commit 1f4cd6504a
125 changed files with 2314 additions and 1981 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ type DialogInfo = {
paragraph?: string;
positiveText?: string;
negativeText?: string;
positivePress?: (value: unknown) => void;
positivePress?: (value: any) => void;
onClose?: () => void;
positiveType?:
| "transparent"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ const TagItem = React.memo(
>
#
</Heading>
{item.alias}
{item.title}
</Heading>
<Paragraph
color={colors.secondary.paragraph}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,7 @@ interface InputProps extends TextInputProps {
color: ColorValue;
onPress: () => void;
testID?: string;
size?: number;
};
buttons?: React.ReactNode;
onBlurInput?: () => void;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = {
});
}
};
/*
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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