mobile: ui updates

This commit is contained in:
Ammar Ahmed
2026-07-24 12:10:59 +05:00
parent 980e214f90
commit 654a221cd2
16 changed files with 2814 additions and 2609 deletions

View File

@@ -382,6 +382,13 @@ const ExportNotesSheet = ({
ExportNotesSheet.present = async (ids?: string[], allNotes?: boolean) => {
const exportNoteIds = allNotes ? await db.notes.all?.ids() : ids || [];
if (!exportNoteIds.length) {
ToastManager.show({
message: strings.noNotesToExport(),
type: "info"
});
return;
}
presentSheet({
component: (ref, close, update) => (
<ExportNotesSheet ids={exportNoteIds} update={update} close={close} />

View File

@@ -320,7 +320,7 @@ export function FormInput({
onPress && loading ? colors.primary.accent : colors.primary.paragraph,
paddingTop: Spacing.LEVEL_3,
paddingBottom: Spacing.LEVEL_3,
lineHeight: fontSize + fontSize * 0.3,
lineHeight: fontSize + fontSize * 0.2,
flexGrow: 1,
flexShrink: 1,
fontFamily: "Inter-Regular",

View File

@@ -18,26 +18,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { db } from "../../../common/database";
import { Spacing } from "../../../common/design/spacing";
import { Button } from "../../../components/ui/button";
import FormInput, {
createFormRef,
validators
} from "../../../components/ui/input/form-input";
import Heading from "../../../components/ui/typography/heading";
import Paragraph from "../../../components/ui/typography/paragraph";
import { eSendEvent, ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { eUserLoggedIn } from "../../../utils/events";
import { DefaultAppStyles } from "../../../utils/styles";
import { Spacing } from "../../../common/design/spacing";
import { PASSWORD_PLACEHOLDER } from "../../../utils/constants";
enum EmailChangeSteps {
verify,
changeEmail
}
const RESEND_TIMEOUT = 30;
export const ChangeEmail = () => {
const { colors } = useThemeColors();
const [step, setStep] = useState(EmailChangeSteps.verify);
@@ -49,10 +51,30 @@ export const ChangeEmail = () => {
})
);
const [loading, setLoading] = useState(false);
const [resendSeconds, setResendSeconds] = useState(RESEND_TIMEOUT);
const emailInputRef = useRef<TextInput>(null);
const passInputRef = useRef<TextInput>(null);
const codeInputRef = useRef<TextInput>(null);
useEffect(() => {
if (step !== EmailChangeSteps.changeEmail || resendSeconds <= 0) return;
const timer = setTimeout(() => {
setResendSeconds((seconds) => seconds - 1);
}, 1000);
return () => clearTimeout(timer);
}, [step, resendSeconds]);
const onResend = useCallback(async () => {
if (resendSeconds > 0) return;
try {
const { email } = formRef.current.getValues();
await db.user?.sendVerificationEmail(email);
setResendSeconds(RESEND_TIMEOUT);
} catch (e) {
formRef.current.setError("code", (e as Error).message);
}
}, [resendSeconds]);
const onSubmit = async () => {
try {
if (step === EmailChangeSteps.verify) {
@@ -67,6 +89,7 @@ export const ChangeEmail = () => {
if (!verified) throw new Error(strings.passwordIncorrect());
await db.user?.sendVerificationEmail(email);
setStep(EmailChangeSteps.changeEmail);
setResendSeconds(RESEND_TIMEOUT);
formRef.current.clearErrors();
formRef.current.setValue("code", "");
setLoading(false);
@@ -113,67 +136,98 @@ export const ChangeEmail = () => {
gap: Spacing.LEVEL_4
}}
>
<View
style={{
gap: Spacing.LEVEL_2
}}
>
{step === EmailChangeSteps.verify ? (
<>
<FormInput
name="email"
formRef={formRef}
label={strings.enterNewEmail()}
fwdRef={emailInputRef}
placeholder={"you@example.com"}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
validators={[
validators.required(strings.emailRequired()),
validators.email(strings.enterValidEmail())
]}
onSubmitEditing={() => {
passInputRef.current?.focus();
}}
/>
<FormInput
name="password"
label={strings.password()}
formRef={formRef}
fwdRef={passInputRef}
placeholder={PASSWORD_PLACEHOLDER}
secureTextEntry
autoCapitalize="none"
autoCorrect={false}
autoComplete="password"
validators={[validators.required(strings.passwordRequired())]}
onSubmitEditing={onSubmit}
/>
</>
) : (
<>
<FormInput
name="code"
formRef={formRef}
fwdRef={codeInputRef}
placeholder={strings.code()}
keyboardType="number-pad"
autoCapitalize="none"
autoCorrect={false}
maxLength={6}
validators={[
validators.required(strings.enterSixDigitCode()),
(value: string) =>
/^\d{6}$/.test(value.trim())
? undefined
: strings.enterSixDigitCode()
]}
onSubmitEditing={onSubmit}
/>
</>
)}
</View>
{step === EmailChangeSteps.verify ? (
<View
style={{
gap: Spacing.LEVEL_2
}}
>
<FormInput
name="email"
formRef={formRef}
label={strings.enterNewEmail()}
fwdRef={emailInputRef}
placeholder={strings.enterEmail()}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
validators={[
validators.required(strings.emailRequired()),
validators.email(strings.enterValidEmail())
]}
onSubmitEditing={() => {
passInputRef.current?.focus();
}}
/>
<FormInput
name="password"
label={strings.enterAccountPassword()}
formRef={formRef}
fwdRef={passInputRef}
placeholder={strings.password()}
secureTextEntry
autoCapitalize="none"
autoCorrect={false}
autoComplete="password"
validators={[validators.required(strings.passwordRequired())]}
onSubmitEditing={onSubmit}
/>
</View>
) : (
<View
style={{
gap: Spacing.LEVEL_4
}}
>
<View
style={{
gap: Spacing.LEVEL_1
}}
>
<Heading fontSize="MD" lineHeight="100%">
{strings.verifyCurrentEmail()}
</Heading>
<Paragraph fontSize="SM" color={colors.secondary.paragraph}>
{strings.verifyCurrentEmailDesc()}
</Paragraph>
</View>
<FormInput
name="code"
formRef={formRef}
label={strings.enterCode()}
fwdRef={codeInputRef}
placeholder={strings.code()}
keyboardType="number-pad"
autoCapitalize="none"
autoCorrect={false}
maxLength={6}
validators={[
validators.required(strings.enterSixDigitCode()),
(value: string) =>
/^\d{6}$/.test(value.trim())
? undefined
: strings.enterSixDigitCode()
]}
onSubmitEditing={onSubmit}
/>
<Paragraph
fontSize="SM"
onPress={onResend}
style={{
marginTop: -Spacing.LEVEL_1
}}
color={
resendSeconds > 0
? colors.secondary.paragraph
: colors.primary.accent
}
>
{resendSeconds > 0
? strings.resend2faCode(`${resendSeconds}`)
: strings.resendCode()}
</Paragraph>
</View>
)}
<Button
title={
@@ -181,7 +235,7 @@ export const ChangeEmail = () => {
? undefined
: step === EmailChangeSteps.verify
? strings.continue()
: strings.changeEmail()
: strings.verify()
}
type="accent"
style={{

View File

@@ -50,6 +50,7 @@ import {
} from "../manage-inbox-keys";
import { FailedInboxItems } from "../failed-inbox-items";
import AccountCard from "./account-card";
import { EditProfile } from "./edit-profile";
export const components: { [name: string]: ReactElement } = {
homeselector: <HomePicker />,
@@ -81,5 +82,6 @@ export const components: { [name: string]: ReactElement } = {
"inbox-keys": <InboxKeysList />,
"failed-inbox-items": <FailedInboxItems />,
"setup-inbox-keys": <SetupInboxKeys />,
"account-card": <AccountCard />
"account-card": <AccountCard />,
"edit-profile": <EditProfile />
};

View File

@@ -0,0 +1,204 @@
/*
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 { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import { Image, TextInput, View } from "react-native";
import { openPicker } from "react-native-image-crop-picker";
import { db } from "../../../common/database";
import { Radius, Spacing } from "../../../common/design/spacing";
import AppIcon from "../../../components/ui/AppIcon";
import { Button } from "../../../components/ui/button";
import FormInput, {
createFormRef
} from "../../../components/ui/input/form-input";
import { Pressable } from "../../../components/ui/pressable";
import { ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useUserStore } from "../../../stores/use-user-store";
const AVATAR_SIZE = 105;
const AVATAR_BADGE_SIZE = 32;
export const EditProfile = () => {
const { colors } = useThemeColors();
const profile = useUserStore((state) => state.profile);
const formRef = useRef(
createFormRef({
fullName: profile?.fullName || ""
})
);
const nameInputRef = useRef<TextInput>(null);
const [name, setName] = useState(profile?.fullName || "");
const [profilePicture, setProfilePicture] = useState(profile?.profilePicture);
const [loading, setLoading] = useState(false);
const isPicking = useRef(false);
const isDirty =
name.trim() !== (profile?.fullName || "").trim() ||
profilePicture !== profile?.profilePicture;
const onPickImage = async () => {
if (isPicking.current) return;
try {
const image = await openPicker({
width: 512,
height: 512,
cropping: true,
cropperCircleOverlay: true,
mediaType: "photo",
includeBase64: true
});
setProfilePicture(
image.data ? `data:${image.mime};base64,${image.data}` : image.path
);
isPicking.current = false;
} catch (e) {
isPicking.current = false;
/* user cancelled */
}
};
const onSave = async () => {
try {
setLoading(true);
const fullName = formRef.current.getValue("fullName").trim();
await db.settings.setProfile({
fullName: fullName || undefined,
profilePicture: profilePicture || undefined
});
useUserStore.setState({
profile: db.settings.getProfile()
});
ToastManager.show({
heading: strings.fullNameUpdated(),
type: "success",
context: "global"
});
Navigation.goBack();
} catch (e) {
ToastManager.error(e as Error, undefined, "global");
} finally {
setLoading(false);
}
};
return (
<View
style={{
paddingHorizontal: Spacing.LEVEL_3,
paddingTop: Spacing.LEVEL_4,
gap: Spacing.LEVEL_4
}}
>
<View
style={{
gap: Spacing.LEVEL_3,
alignItems: "center"
}}
>
<Pressable
type="transparent"
onPress={onPickImage}
style={{
width: AVATAR_SIZE,
height: AVATAR_SIZE,
borderRadius: AVATAR_SIZE
}}
>
{profilePicture ? (
<Image
source={{ uri: profilePicture }}
style={{
width: AVATAR_SIZE,
height: AVATAR_SIZE,
borderRadius: AVATAR_SIZE
}}
/>
) : (
<View
style={{
width: AVATAR_SIZE,
height: AVATAR_SIZE,
borderRadius: AVATAR_SIZE,
alignItems: "center",
justifyContent: "center",
backgroundColor: colors.secondary.background
}}
>
<AppIcon
name="user"
iconFamily="notesnook"
size={AVATAR_SIZE / 2.5}
color={colors.secondary.icon}
/>
</View>
)}
<View
style={{
position: "absolute",
right: 0,
bottom: 0,
width: AVATAR_BADGE_SIZE,
height: AVATAR_BADGE_SIZE,
borderRadius: AVATAR_BADGE_SIZE,
alignItems: "center",
justifyContent: "center",
borderWidth: 2,
borderColor: colors.primary.background,
backgroundColor: colors.secondary.background
}}
>
<AppIcon
name="mode-edit"
iconFamily="notesnook"
size={16}
color={colors.primary.icon}
/>
</View>
</Pressable>
<FormInput
name="fullName"
formRef={formRef}
fwdRef={nameInputRef}
label={strings.name()}
placeholder={strings.name()}
wrapperStyle={{ width: "100%" }}
containerStyle={{ borderRadius: Radius.XS }}
onChangeText={setName}
onSubmitEditing={onSave}
/>
</View>
<Button
title={strings.saveChanges()}
type="accent"
loading={loading}
disabled={!isDirty}
onPress={onSave}
style={{ width: "100%" }}
/>
</View>
);
};
export default EditProfile;

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { FlatList, Platform, View } from "react-native";
import { ActivityIndicator, FlatList, Platform, View } from "react-native";
import NotificationSounds, {
playSampleSound,
Sound,
@@ -34,6 +34,10 @@ import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../../utils/size";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import { Header } from "../../../components/header";
import LineSeparator from "../../../components/ui/seperator/line-separator";
import { sleep } from "../../../utils/time";
import { Spacing } from "../../../common/design/spacing";
const SoundItem = ({
playingSoundId,
@@ -127,43 +131,80 @@ const SoundItem = ({
};
export default function SoundPicker() {
const { colors } = useThemeColors();
const [sounds, setSounds] = useState<Sound[]>([]);
const [ringtones, setRingtones] = useState<Sound[]>([]);
const [playing, setPlaying] = useState<Sound | undefined>();
const [loading, setLoading] = useState(true);
const notificationSound = useSettingStore(
(state) => state.settings.notificationSound
);
useEffect(() => {
NotificationSounds.getNotifications("ringtone").then((results) =>
setRingtones([
{
soundID: "defaultSound",
title: strings.defaultSound(),
url: ""
},
...results
])
);
NotificationSounds.getNotifications("notification").then((results) =>
setSounds([...results])
);
async function getSounds() {
try {
const ringtones = await NotificationSounds.getNotifications("ringtone");
const sounds =
await NotificationSounds.getNotifications("notification");
setRingtones([
{
soundID: "defaultSound",
title: strings.defaultSound(),
url: ""
},
...ringtones
]);
setSounds([...sounds]);
} finally {
setLoading(false);
}
}
getSounds();
}, []);
return (
<View>
<FlatList
data={[...sounds, ...ringtones]}
renderItem={({ item, index }) => (
<SoundItem
playingSoundId={playing?.soundID}
selectedSoundId={notificationSound?.soundID}
item={item}
index={index}
setPlaying={setPlaying}
/>
)}
<View
style={{
flex: 1
}}
>
<Header
title={strings.changeNotificationSound()}
canGoBack
style={{
backgroundColor: "transparent"
}}
/>
<LineSeparator />
{loading ? (
<View
style={{
alignItems: "center",
justifyContent: "center",
gap: Spacing.LEVEL_2,
flex: 1
}}
>
<ActivityIndicator size="small" color={colors.primary.accent} />
<Paragraph color={colors.secondary.paragraph}>
{strings.loading()}
</Paragraph>
</View>
) : (
<FlatList
data={[...sounds, ...ringtones]}
renderItem={({ item, index }) => (
<SoundItem
playingSoundId={playing?.soundID}
selectedSoundId={notificationSound?.soundID}
item={item}
index={index}
setPlaying={setPlaying}
/>
)}
/>
)}
</View>
);
}

View File

@@ -62,99 +62,6 @@ export const accountGroup: SettingSection = {
useHook: () => useUserStore((state) => state.user),
hidden: (current) => !current,
sections: [
// {
// id: "subscription-status",
// useHook: () => useUserStore((state) => state.user),
// hidden: (current) => {
// const user = current as User;
// return (
// !user ||
// !user.subscription ||
// user.subscription.provider === undefined ||
// !strings.subscriptionProviderInfo[user?.subscription?.provider] ||
// user.subscription?.plan === SubscriptionPlan.FREE
// );
// },
// name: (current) => {
// const user = (current as User) || useUserStore.getState().user;
// return (
// strings.subscriptionProviderInfo[
// user?.subscription?.provider
// ]?.title() || `Unknown provider id: ${user?.subscription?.provider}`
// );
// },
// icon: "credit-card",
// modifer: () => {
// const user = useUserStore.getState().user;
// if (!user) return;
// const subscriptionProviderInfo =
// strings.subscriptionProviderInfo[user?.subscription?.provider];
// if (!subscriptionProviderInfo) return;
// const isCurrentPlatform =
// (user.subscription?.provider === SubscriptionProvider.APPLE &&
// Platform.OS === "ios") ||
// (user.subscription?.provider === SubscriptionProvider.GOOGLE &&
// Platform.OS === "android");
// if (
// (user.subscription?.provider === SubscriptionProvider.GOOGLE ||
// user.subscription?.provider === SubscriptionProvider.APPLE) &&
// isCurrentPlatform &&
// user?.subscription?.productId
// ) {
// RNIap.deepLinkToSubscriptions({
// sku: user?.subscription.productId
// });
// } else {
// presentSheet({
// title: subscriptionProviderInfo.title(),
// paragraph: subscriptionProviderInfo.desc()
// });
// }
// },
// description: (current) => {
// const user = current as User;
// if (!user) return strings.neverHesitate();
// const subscriptionDaysLeft =
// user && getTimeLeft(user.subscription?.expiry);
// const expiryDate = dayjs(user?.subscription?.expiry).format(
// "dddd, MMMM D, YYYY h:mm A"
// );
// const startDate = dayjs(user?.subscription?.start).format(
// "dddd, MMMM D, YYYY h:mm A"
// );
// const trialEndDate = dayjs(user?.subscription?.start)
// .add(
// user?.subscription?.productId?.includes("monthly") ? 7 : 14,
// "day"
// )
// .format("dddd, MMMM D, YYYY h:mm A");
// if (
// user.subscription?.plan !== SubscriptionPlan.FREE &&
// user.subscription?.productId
// ) {
// const status = user.subscription?.status;
// return status === SubscriptionStatus.TRIAL
// ? strings.trialOnGoing(trialEndDate)
// : status === SubscriptionStatus.ACTIVE
// ? strings.subRenewOn(expiryDate)
// : status === SubscriptionStatus.CANCELED ||
// status === SubscriptionStatus.PAUSED
// ? strings.subEndsOn(expiryDate)
// : status === SubscriptionStatus.EXPIRED
// ? subscriptionDaysLeft.time < -3
// ? strings.subEnded()
// : strings.accountDowngradedIn(3)
// : strings.neverHesitate();
// }
// return strings.neverHesitate();
// }
// },
{
id: "redeem-gift-code",
name: strings.redeemGiftCode(),
@@ -222,76 +129,17 @@ export const accountGroup: SettingSection = {
description: strings.editProfileDesc(),
icon: "user",
iconFamily: "notesnook",
sections: [
{
id: "remove-profile-picture",
icon: "trash",
iconFamily: "notesnook",
name: strings.removeProfilePicture(),
description: strings.removeProfilePictureDesc(),
useHook: () =>
useUserStore((state) => state.profile?.profilePicture),
hidden: () =>
!useUserStore.getState().profile?.profilePicture,
isModal: true,
modifer: () => {
presentDialog({
title: strings.removeProfilePicture(),
paragraph: strings.removeProfilePictureConfirmation(),
positiveText: strings.remove(),
positivePress: async () => {
db.settings
.setProfile({
profilePicture: undefined
})
.then(async () => {
useUserStore.setState({
profile: db.settings.getProfile()
});
});
}
});
}
},
{
id: "remove-name",
icon: "trash",
iconFamily: "notesnook",
name: strings.removeFullName(),
description: strings.removeFullNameDesc(),
useHook: () =>
useUserStore((state) => state.profile?.fullName),
hidden: () => !useUserStore.getState().profile?.fullName,
isModal: true,
modifer: () => {
presentDialog({
title: strings.removeFullName(),
paragraph: strings.removeFullNameConfirmation(),
positiveText: strings.remove(),
positivePress: async () => {
db.settings
.setProfile({
fullName: undefined
})
.then(async () => {
useUserStore.setState({
profile: db.settings.getProfile()
});
});
}
});
}
},
{
id: "change-email",
name: strings.changeEmail(),
type: "screen",
component: "change-email",
description: strings.changeEmailDesc(),
icon: "at",
headerBottomBorder: true
}
]
component: "edit-profile"
},
{
id: "change-email",
name: strings.changeEmail(),
type: "screen",
component: "change-email",
description: strings.changeEmailDesc(),
icon: "at",
headerBottomBorder: true
},
{
id: "subscription-not-active",

View File

@@ -280,7 +280,7 @@ export const customizeGroup: SettingSection = {
maxInputValue: 400,
step: 1,
inputBadgeValue: "px",
icon: "text-aa",
icon: "format-font-size",
iconFamily: "notesnook",
property: "defaultFontSize"
},
@@ -290,8 +290,7 @@ export const customizeGroup: SettingSection = {
description: strings.lineHeightDesc(),
type: "input-selector",
property: "defaultLineHeight",
icon: "list",
iconSize: 8,
icon: "format-arrows-vertical",
iconFamily: "notesnook",
minInputValue: EDITOR_LINE_HEIGHT.MIN,
maxInputValue: EDITOR_LINE_HEIGHT.MAX,

View File

@@ -90,7 +90,7 @@ export const productivityGroup: SettingSection = {
validators: [
validators.number(),
validators.custom((value) => {
return value < 4
return value < 5
? strings.valueMustBeGreaterThan("4")
: undefined;
})
@@ -110,6 +110,7 @@ export const productivityGroup: SettingSection = {
name: strings.changeNotificationSound(),
description: strings.changeNotificationSoundDesc(),
component: "sound-picker",
hideHeader: true,
icon: "speaker-high",
iconFamily: "notesnook",
hidden: () => Platform.OS === "android"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.375 3.0625V4.8125C11.375 4.92853 11.3289 5.03981 11.2469 5.12186C11.1648 5.20391 11.0535 5.25 10.9375 5.25C10.8215 5.25 10.7102 5.20391 10.6281 5.12186C10.5461 5.03981 10.5 4.92853 10.5 4.8125V3.5H7.4375V10.5H8.75C8.86603 10.5 8.97731 10.5461 9.05936 10.6281C9.14141 10.7102 9.1875 10.8215 9.1875 10.9375C9.1875 11.0535 9.14141 11.1648 9.05936 11.2469C8.97731 11.3289 8.86603 11.375 8.75 11.375H5.25C5.13397 11.375 5.02269 11.3289 4.94064 11.2469C4.85859 11.1648 4.8125 11.0535 4.8125 10.9375C4.8125 10.8215 4.85859 10.7102 4.94064 10.6281C5.02269 10.5461 5.13397 10.5 5.25 10.5H6.5625V3.5H3.5V4.8125C3.5 4.92853 3.45391 5.03981 3.37186 5.12186C3.28981 5.20391 3.17853 5.25 3.0625 5.25C2.94647 5.25 2.83519 5.20391 2.75314 5.12186C2.67109 5.03981 2.625 4.92853 2.625 4.8125V3.0625C2.625 2.94647 2.67109 2.83519 2.75314 2.75314C2.83519 2.67109 2.94647 2.625 3.0625 2.625H10.9375C11.0535 2.625 11.1648 2.67109 11.2469 2.75314C11.3289 2.83519 11.375 2.94647 11.375 3.0625Z" fill="#181818"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1547,7 +1547,12 @@ $day$: Current day (eg. Monday)`,
enterNotebookDescription: () => t`Enter notebook description`,
searchNotebooks: () => t`Search notebooks`,
enterNewEmail: () => t`Enter your new email`,
enterEmail: () => t`Enter email`,
verifyNewEmail: () => t`Enter verification code sent to your new email`,
verifyCurrentEmail: () => t`Verify your current email`,
verifyCurrentEmailDesc: () =>
t`We've sent a verification code to your new email. Enter it below to continue.`,
enterCode: () => t`Enter code`,
issueTitlePlaceholder: () => t`Tell us what happened`,
issuePlaceholder: () => t`Tell us more about the issue you are facing.
@@ -3054,5 +3059,6 @@ Continue without attachments?`,
t`Changes from other devices won't be updated in the editor in real-time.`,
disableSync: () => t`Disable sync`,
disableSyncDesc: () =>
t`Turns off syncing completely on this device. Any changes made will remain local only and new changes from your other devices won't sync to this device.`
t`Turns off syncing completely on this device. Any changes made will remain local only and new changes from your other devices won't sync to this device.`,
noNotesToExport: () => t`No notes to export`
};