Compare commits

...

8 Commits

Author SHA1 Message Date
01zulfi
9c497fd2bb Merge pull request #9724 from streetwriters/release/3.3.15
web: bump version to 3.3.15
2026-04-20 12:21:46 +05:00
Abdullah Atta
ede900fdbd ci: add linux arm64 preview builds (#9735) 2026-04-20 11:56:10 +05:00
01zulfi
1758f03f93 intl: regen locale files (#9734)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-20 11:39:13 +05:00
01zulfi
f18db748e0 web: bump version to 3.3.15
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-04-20 10:38:18 +05:00
01zulfi
2fba2af126 web: fix pdf viewer's search dialog unreadable UI in dark mode (#9720)
* web: fix pdf viewer's search dialog unreadable UI in dark mode
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: fix pdf search dialog ui

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2026-04-18 10:46:09 +05:00
Ammar Ahmed
1ef3d712e1 mobile: fix parent notebook can be moved into it's child notebooks. (#9691)
* mobile: fix parent notebook can be moved into it's child notebooks.

1. Ensure notebook that is being moved and all it's children are not visible in move-notebook dialog
2. Ensure child cannot be moved into it's parent again
3. Ensure child can be moved into it's sibling notebooks

* mobile: add e2e tests

* mobile: fix toast position
2026-04-18 09:28:39 +05:00
Ammar Ahmed
405adff8f4 mobile: fix linting not working in vscode and fix lint errors (#9710) 2026-04-18 08:52:39 +05:00
Ammar Ahmed
9a21ca1cf4 mobile: fix discount value shows as undefined on iOS when buying plan (#9711)
* mobile: fix discount value is undefined

* mobile: fix discount calculation and price formatting

Respect spacing when writing price in a currency.
Fix incorrect discount calculation when comparing products of a plan based on int values, use parseFloat to parse correct values for comparision

* mobile: fix ui inconsistencies

* mobile: conditionally include trial plan conditions based on GitHub release status
2026-04-18 08:40:50 +05:00
87 changed files with 875 additions and 3047 deletions

View File

@@ -101,7 +101,6 @@ jobs:
- name: Build dmg
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
@@ -153,8 +152,6 @@ jobs:
run: npm run tx @notesnook/desktop:release
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 -p never
working-directory: ./apps/desktop
@@ -165,6 +162,49 @@ jobs:
name: linux-x64-build
path: apps/desktop/output/*.AppImage
build-linux-arm64:
name: Build for Linux arm64
needs: build
runs-on: ubuntu-22.04-arm
outputs:
linux-arm64-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
- name: Build AppImage
run: yarn electron-builder --config=electron-builder.config.js --linux AppImage:arm64 -p never
working-directory: ./apps/desktop
- name: Upload AppImage artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
name: linux-arm64-build
path: apps/desktop/output/*.AppImage
build-windows:
name: Build for Windows
needs: build
@@ -216,7 +256,8 @@ jobs:
post-pr-comment:
name: Post PR comment with preview URLs
needs: [build, build-macos, build-linux-x64, build-windows]
needs:
[build, build-macos, build-linux-x64, build-linux-arm64, build-windows]
runs-on: ubuntu-latest
steps:
- name: Post or update PR comment
@@ -224,6 +265,7 @@ jobs:
env:
macos_artifact_url: ${{ needs.build-macos.outputs.macos-artifact-url }}
linux_x64_artifact_url: ${{ needs.build-linux-x64.outputs.linux-x64-artifact-url }}
linux_arm64_artifact_url: ${{ needs.build-linux-arm64.outputs.linux-arm64-artifact-url }}
windows_artifact_url: ${{ needs.build-windows.outputs.windows-artifact-url }}
with:
script: |
@@ -232,6 +274,7 @@ jobs:
const previewUrl = [
{ platform: 'macOS', url: process.env.macos_artifact_url },
{ platform: 'Linux x64', url: process.env.linux_x64_artifact_url },
{ platform: 'Linux arm64', url: process.env.linux_arm64_artifact_url },
{ platform: 'Windows x64', url: process.env.windows_artifact_url },
].filter(u => u.url).map(u => `- [${u.platform}](${u.url})`).join('\n');
const body = `${marker}\n**Desktop Previews**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.14",
"version": "3.3.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.14",
"version": "3.3.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.14",
"version": "3.3.15",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -23,7 +23,7 @@ import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { PropsWithChildren, useEffect } from "react";
import { Appearance, I18nManager, Linking, StatusBar } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
@@ -106,8 +106,10 @@ let currTheme =
: SettingsService.getProperty("lighTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element: (props: any) => JSX.Element) => {
return function AppWithThemeProvider(props: any) {
export const withTheme = (
Element: (props: PropsWithChildren) => JSX.Element
) => {
return function AppWithThemeProvider(props: PropsWithChildren) {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
state.colorScheme,
state.darkTheme,

View File

@@ -25,7 +25,6 @@ import * as Keychain from "react-native-keychain";
import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
import { generateSecureRandom } from "react-native-securerandom";
import { DatabaseLogger } from ".";
import { ToastManager } from "../../services/event-manager";
import { MMKV } from "./mmkv";
// Database key cipher is persisted across different user sessions hence it has
@@ -205,9 +204,8 @@ export async function getDatabaseKey(appLockPassword?: string) {
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
const userKeyCredentials =
await Keychain.getInternetCredentials("notesnook");
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(

View File

@@ -72,7 +72,7 @@ export async function setupDatabase(password?: string) {
({
compress: Gzip.deflate,
decompress: Gzip.inflate
} as ICompressor),
}) as ICompressor,
batchSize: 50,
sqliteOptions: {
dialect: (name) => ({

View File

@@ -109,8 +109,8 @@ class RNSqliteConnection implements DatabaseConnection {
query.kind === "SelectQueryNode"
? "query"
: query.kind === "RawNode"
? "raw"
: "exec";
? "raw"
: "exec";
const result = await this.db.executeAsync(sql, parameters as any[]);

View File

@@ -112,8 +112,8 @@ export async function writeEncryptedBase64(
async function deleteLocalFile(filename: string) {
try {
await createCacheDir();
let path = cacheDir + `/${filename}`;
let exists = await RNFetchBlob.fs.exists(path);
const path = cacheDir + `/${filename}`;
const exists = await RNFetchBlob.fs.exists(path);
if (Platform.OS === "ios" && !exists) {
const iosAppGroup =
Platform.OS === "ios"
@@ -309,7 +309,9 @@ export async function deleteDCacheFiles() {
});
}
}
} catch (e) {}
} catch (e) {
/** Empty */
}
}
export async function getCachePathForFile(filename: string) {

View File

@@ -36,7 +36,7 @@ export const Announcement = () => {
state.announcements,
state.remove
]);
let announcement = announcements.length > 0 ? announcements[0] : null;
const announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore((state) => state.selectionMode);
return !announcement || selectionMode ? null : (

View File

@@ -32,7 +32,7 @@ import { Action } from "../../stores/use-message-store";
export const Cta = (props: BodyItemProps) => {
const { colors } = useThemeColors();
let buttons =
const buttons =
props.item.actions.filter((item) => allowedOnPlatform(item.platforms)) ||
[];

View File

@@ -53,7 +53,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { strings } from "@notesnook/intl";
import { AppFontSize } from "../../utils/size";
import { editorController } from "../../screens/editor/tiptap/utils";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
@@ -239,8 +238,8 @@ const AppLocked = () => {
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,

View File

@@ -46,7 +46,6 @@ import {
eOnLoadNote
} from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { openNote } from "../list-items/note/wrapper";

View File

@@ -17,7 +17,6 @@ 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 { LegendList } from "@legendapp/list";
import {
Attachment,
FilteredSelector,

View File

@@ -53,7 +53,7 @@ export const ForgotPassword = ({ userEmail }: { userEmail: string }) => {
}
setLoading(true);
try {
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
const lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
if (
lastRecoveryEmailTime &&
Date.now() - lastRecoveryEmailTime < 60000 * 3

View File

@@ -22,14 +22,9 @@ import { useThemeColors } from "@notesnook/theme";
import { RouteProp, useRoute } from "@react-navigation/native";
import React, { useEffect, useState } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
@@ -195,8 +190,8 @@ export const Login = ({
? "50%"
: "49.99%"
: focused
? "100%"
: "99.9%",
? "100%"
: "99.9%",
backgroundColor: colors.primary.background,
alignSelf: "center",
paddingHorizontal: DDS.isTab ? 0 : DefaultAppStyles.GAP,

View File

@@ -92,7 +92,7 @@ export const SessionExpired = () => {
const open = React.useCallback(async () => {
try {
let res = await db.tokenManager.getToken();
const res = await db.tokenManager.getToken();
if (!res) throw new Error("no token found");
if (db.tokenManager._isTokenExpired(res))
throw new Error("token expired");
@@ -102,7 +102,7 @@ export const SessionExpired = () => {
Sync.run("global", false, "full", async (complete) => {
if (!complete) {
let user = await db.user.getUser();
const user = await db.user.getUser();
if (!user) return;
email.current = user.email;
setVisible(true);
@@ -115,7 +115,7 @@ export const SessionExpired = () => {
setVisible(false);
});
} catch (e) {
let user = await db.user.getUser();
const user = await db.user.getUser();
if (!user) return;
email.current = user.email;
setFocused(false);

View File

@@ -99,7 +99,7 @@ export const Signup = ({
try {
setCurrentStep(SignupSteps.createAccount);
await db.user.signup(email.current!.toLowerCase(), password.current!);
let user = await db.user.getUser();
const user = await db.user.getUser();
setUser(user);
setLastSynced(await db.lastSynced());
clearMessage();

View File

@@ -1,3 +1,22 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { useThemeColors } from "@notesnook/theme";
import { useRef } from "react";
import { View } from "react-native";

View File

@@ -23,7 +23,6 @@ import {
ColorValue,
KeyboardAvoidingView,
Modal,
Platform,
SafeAreaView,
StyleSheet,
TouchableOpacity,
@@ -136,8 +135,8 @@ const BaseDialog = ({
backgroundColor: background
? background
: transparent
? "transparent"
: "rgba(0,0,0,0.3)"
? "transparent"
: "rgba(0,0,0,0.3)"
}}
>
<KeyboardAvoidingView
@@ -155,8 +154,8 @@ const BaseDialog = ({
justifyContent: centered
? "center"
: bottom
? "flex-end"
: "flex-start"
? "flex-end"
: "flex-start"
}
]}
>

View File

@@ -22,7 +22,6 @@ import { Text, View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../utils/size";
import { Button, ButtonProps } from "../ui/button";
import { PressableProps } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";

View File

@@ -110,61 +110,6 @@ export const VaultDialog: React.FC = () => {
const confirmPasswordRef = useRef<string | null>(null);
const newPasswordRef = useRef<string | null>(null);
const open = useCallback(async (data: Vault) => {
const biometry = await BiometricService.isBiometryAvailable();
const available = !!biometry;
const fingerprint = await BiometricService.hasInternetCredentials();
if (data.item) {
const locked = await db.vaults.itemExists(data.item);
noteLockedRef.current = locked;
if (!locked) {
const content = await db.content.findByNoteId(data.item!.id);
if (content && isEncryptedContent(content)) {
noteLockedRef.current = true;
}
}
}
// Set refs
noteRef.current = data.item;
titleRef.current = data.title || strings.goToEditor();
descriptionRef.current = data.description || null;
paragraphRef.current = data.paragraph || null;
buttonTitleRef.current = data.buttonTitle || null;
positiveButtonTypeRef.current = data.positiveButtonType || "transparent";
customActionTitleRef.current = data.customActionTitle || null;
customActionParagraphRef.current = data.customActionParagraph || null;
onUnlockRef.current = data.onUnlock;
requestTypeRef.current = data.requestType;
// Set UI state
setIsBiometryAvailable(available);
setIsBiometryEnrolled(fingerprint);
setBiometricUnlock(fingerprint);
setWrongPassword(false);
setPasswordsDontMatch(false);
setDeleteAll(false);
setLoading(false);
// Auto-unlock with fingerprint if applicable
const canAutoUnlock =
fingerprint &&
data.requestType !== VaultRequestType.EnableFingerprint &&
data.requestType !== VaultRequestType.RevokeFingerprint &&
data.requestType !== VaultRequestType.ChangePassword &&
data.requestType !== VaultRequestType.ClearVault &&
data.requestType !== VaultRequestType.DeleteVault &&
data.requestType !== VaultRequestType.CustomAction &&
data.requestType !== VaultRequestType.PermanentUnlock;
if (canAutoUnlock) {
await onPressFingerprintAuth(data.title, data.description);
} else {
setVisible(true);
}
}, []);
const close = useCallback(() => {
if (loading) {
ToastManager.show({
@@ -292,6 +237,45 @@ export const VaultDialog: React.FC = () => {
setLoading(false);
}, [close]);
const enrollFingerprint = useCallback(
async (password: string) => {
setLoading(true);
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
setLoading(false);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: strings.biometricUnlockEnabled(),
type: "success",
context: "global"
});
close();
} catch (e) {
close();
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
setLoading(false);
}
},
[close]
);
const takeErrorAction = useCallback(() => {
setWrongPassword(true);
setVisible(true);
setTimeout(() => {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
}, 500);
}, []);
const lockNote = useCallback(async () => {
if (!passwordRef.current || passwordRef.current.trim() === "") {
ToastManager.show({
@@ -333,7 +317,13 @@ export const VaultDialog: React.FC = () => {
.catch((e) => {
takeErrorAction();
});
}, [close, biometricUnlock, isBiometryEnrolled]);
}, [
biometricUnlock,
isBiometryEnrolled,
close,
enrollFingerprint,
takeErrorAction
]);
const openInEditor = useCallback(
(note: Note & { content?: NoteContent<false> }) => {
@@ -387,19 +377,7 @@ export const VaultDialog: React.FC = () => {
} catch (e) {
takeErrorAction();
}
}, [close]);
const takeErrorAction = useCallback(() => {
setWrongPassword(true);
setVisible(true);
setTimeout(() => {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
}, 500);
}, []);
}, [close, takeErrorAction]);
const openNote = useCallback(async () => {
try {
@@ -440,6 +418,7 @@ export const VaultDialog: React.FC = () => {
}, [
biometricUnlock,
isBiometryEnrolled,
enrollFingerprint,
openInEditor,
shareNote,
deleteNote,
@@ -464,33 +443,6 @@ export const VaultDialog: React.FC = () => {
}
}, [permanantUnlock, openNote]);
const enrollFingerprint = useCallback(
async (password: string) => {
setLoading(true);
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
setLoading(false);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: strings.biometricUnlockEnabled(),
type: "success",
context: "global"
});
close();
} catch (e) {
close();
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "local"
});
setLoading(false);
}
},
[close]
);
const createVault = useCallback(async () => {
await db.vault.create(passwordRef.current || "");
@@ -536,31 +488,6 @@ export const VaultDialog: React.FC = () => {
}
}, []);
const onPressFingerprintAuth = useCallback(
async (title?: string, description?: string) => {
try {
const credentials = await BiometricService.getCredentials(
title || titleRef.current,
description || descriptionRef.current || ""
);
if (!credentials) throw new Error("Failed to get user credentials");
if (credentials?.password) {
passwordRef.current = credentials.password;
onPress();
} else {
eSendEvent(eCloseActionSheet);
await sleep(300);
setVisible(true);
}
} catch (e) {
console.error(e);
}
},
[]
);
const onPress = useCallback(async () => {
const requestType = requestTypeRef.current;
@@ -689,6 +616,89 @@ export const VaultDialog: React.FC = () => {
deleteVault
]);
const onPressFingerprintAuth = useCallback(
async (title?: string, description?: string) => {
try {
const credentials = await BiometricService.getCredentials(
title || titleRef.current,
description || descriptionRef.current || ""
);
if (!credentials) throw new Error("Failed to get user credentials");
if (credentials?.password) {
passwordRef.current = credentials.password;
onPress();
} else {
eSendEvent(eCloseActionSheet);
await sleep(300);
setVisible(true);
}
} catch (e) {
console.error(e);
}
},
[onPress]
);
const open = useCallback(
async (data: Vault) => {
const biometry = await BiometricService.isBiometryAvailable();
const available = !!biometry;
const fingerprint = await BiometricService.hasInternetCredentials();
if (data.item) {
const locked = await db.vaults.itemExists(data.item);
noteLockedRef.current = locked;
if (!locked) {
const content = await db.content.findByNoteId(data.item!.id);
if (content && isEncryptedContent(content)) {
noteLockedRef.current = true;
}
}
}
// Set refs
noteRef.current = data.item;
titleRef.current = data.title || strings.goToEditor();
descriptionRef.current = data.description || null;
paragraphRef.current = data.paragraph || null;
buttonTitleRef.current = data.buttonTitle || null;
positiveButtonTypeRef.current = data.positiveButtonType || "transparent";
customActionTitleRef.current = data.customActionTitle || null;
customActionParagraphRef.current = data.customActionParagraph || null;
onUnlockRef.current = data.onUnlock;
requestTypeRef.current = data.requestType;
// Set UI state
setIsBiometryAvailable(available);
setIsBiometryEnrolled(fingerprint);
setBiometricUnlock(fingerprint);
setWrongPassword(false);
setPasswordsDontMatch(false);
setDeleteAll(false);
setLoading(false);
// Auto-unlock with fingerprint if applicable
const canAutoUnlock =
fingerprint &&
data.requestType !== VaultRequestType.EnableFingerprint &&
data.requestType !== VaultRequestType.RevokeFingerprint &&
data.requestType !== VaultRequestType.ChangePassword &&
data.requestType !== VaultRequestType.ClearVault &&
data.requestType !== VaultRequestType.DeleteVault &&
data.requestType !== VaultRequestType.CustomAction &&
data.requestType !== VaultRequestType.PermanentUnlock;
if (canAutoUnlock) {
await onPressFingerprintAuth(data.title, data.description);
} else {
setVisible(true);
}
},
[onPressFingerprintAuth]
);
useEffect(() => {
eSubscribeEvent(eOpenVaultDialog, open);
eSubscribeEvent(eCloseVaultDialog, close);

View File

@@ -22,7 +22,6 @@ import React, {
RefObject,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState
} from "react";
@@ -39,7 +38,6 @@ import Animated, {
WithSpringConfig,
withTiming
} from "react-native-reanimated";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { eSendEvent } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { eClearEditor } from "../../utils/events";

View File

@@ -77,10 +77,10 @@ export default function List(props: ListProps) {
props.renderedInRoute === "Notes"
? "home"
: props.renderedInRoute === "Favorites"
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
const groupOptions = useGroupOptions(groupType);
@@ -94,7 +94,7 @@ export default function List(props: ListProps) {
(item: number | boolean, index: number) => {
return props.data?.type(index);
},
[]
[props.data]
);
const renderItem = React.useCallback(

View File

@@ -30,7 +30,7 @@ import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import { useIsFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
import { strings } from "@notesnook/intl";
import { ToastManager } from "../../services/event-manager";
@@ -142,7 +142,7 @@ function ReorderableList<T extends { id: string }>({
items.push(...data.filter((i) => !itemOrderState.includes(i.id)));
return items;
}, [data, customizableSidebarFeature?.isAllowed]);
}, [customizableSidebarFeature?.isAllowed, data, itemOrderState]);
return (
<View style={styles.container}>

View File

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { getFormattedDate } from "@notesnook/common";
import {
EncryptedContentItem,
isEncryptedContent,
Note,
UnencryptedContentItem
} from "@notesnook/core";
@@ -75,9 +74,9 @@ const MergeConflicts = () => {
const { height } = useSettingStore((state) => state.dimensions);
const applyChanges = async () => {
let contentToSave = selectedContent;
const contentToSave = selectedContent;
if (!contentToSave) return;
let note = await db.notes.note(contentToSave.noteId);
const note = await db.notes.note(contentToSave.noteId);
if (!note) return;
await db.notes.add({
id: note.id,
@@ -462,7 +461,8 @@ const MergeConflicts = () => {
<ReadonlyEditor
editorId="conflictSecondary"
onLoad={async (loadContent) => {
const note = await db.notes.note(content.current?.noteId!);
if (!content.current?.noteId) return;
const note = await db.notes.note(content.current?.noteId);
if (!note) return;
loadContent({
id: note.id,

View File

@@ -63,22 +63,25 @@ const HistoryItem = ({
}${_end_time}`;
};
const preview = useCallback(async (item: HistorySession) => {
const content = await db.noteHistory.content(item.id);
presentSheet({
component: (
<NotePreview
session={{
...item,
session: getDate(item.dateCreated, item.dateModified)
}}
content={content}
note={note}
/>
),
context: "note_history"
});
}, []);
const preview = useCallback(
async (item: HistorySession) => {
const content = await db.noteHistory.content(item.id);
presentSheet({
component: (
<NotePreview
session={{
...item,
session: getDate(item.dateCreated, item.dateModified)
}}
content={content}
note={note}
/>
),
context: "note_history"
});
},
[note]
);
return (
<Pressable
@@ -138,7 +141,7 @@ export default function NoteHistory({
({ index }: { index: number }) => (
<HistoryItem index={index} items={history} note={note} />
),
[history]
[history, note]
);
return (

View File

@@ -42,7 +42,6 @@ import {
isEncryptedContent,
Note,
NoteContent,
SessionContentItem,
TrashOrItem
} from "@notesnook/core";

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { getFeaturesTable } from "@notesnook/common";
import {
EV,
EVENTS,
Plan,
SKUResponse,
@@ -119,7 +118,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
}
setStep(Steps.buy);
}
}, [routeParams.state]);
}, [pricingPlans, routeParams.state]);
useEffect(() => {
let listener: NativeEventSubscription;
@@ -137,7 +136,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
return () => {
listener?.remove();
};
}, [isFocused, step]);
}, [isFocused, routeParams.context, step]);
useEffect(() => {
const sub = db.eventManager.subscribe(
@@ -154,7 +153,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
return () => {
sub?.unsubscribe();
};
}, []);
}, [routeParams.context]);
const is5YearPlanSelected = (
isGithubRelease
@@ -971,7 +970,7 @@ const PricingPlanCard = ({
.then((value) => {
setRegionaDiscount(value);
});
}, [annualBilling]);
}, [WebPlan?.period, annualBilling, plan.id, pricingPlans]);
useEffect(() => {
if (!annualBilling) {
@@ -1008,8 +1007,8 @@ const PricingPlanCard = ({
: "monthly"
}`
: pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: (product?.productId as string)
? (WebPlan?.period as string)
: (product?.productId as string)
);
setStep(Steps.buy);
}}

View File

@@ -132,7 +132,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
}
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}, []);
}, [colorFeature]);
return (
<>

View File

@@ -35,7 +35,7 @@ export const DateMeta = ({ item }: { item: Item }) => {
const [dateCreated, setDateCreated] = useState(item.dateCreated);
function getDateMeta() {
let keys = Object.keys(item);
const keys = Object.keys(item);
if (keys.includes("dateEdited"))
keys.splice(
keys.findIndex((k) => k === "dateModified"),

View File

@@ -173,15 +173,15 @@ export const Items = ({
DDS.isTab
? AppFontSize.xxl
: shouldShrink
? AppFontSize.xxl
: AppFontSize.lg
? AppFontSize.xxl
: AppFontSize.lg
}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
@@ -212,8 +212,8 @@ export const Items = ({
text: item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.paragraph
: colors.primary.paragraph
? colors.error.paragraph
: colors.primary.paragraph
}}
testID={"icon-" + item.id}
onPress={item.onPress}
@@ -277,8 +277,8 @@ export const Items = ({
item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.icon
: colors.secondary.icon
? colors.error.icon
: colors.secondary.icon
}
/>
@@ -318,7 +318,9 @@ export const Items = ({
[
colors.error.icon,
colors.primary.accent,
colors.primary.border,
colors.secondary.icon,
colors.static.orange,
columnItemWidth,
topBarSorting
]

View File

@@ -77,7 +77,7 @@ export const TagStrip = ({ item, close }) => {
.then((tags) => {
setTags(tags);
});
}, []);
}, [item]);
return tags?.length > 0 ? (
<View

View File

@@ -42,8 +42,6 @@ import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions";
import ExportNotesSheet from "../sheets/export-notes";
import { MoveNotebook } from "../../screens/move-notebook";
import { IconButton } from "../ui/icon-button";
import NativeTooltip from "../../utils/tooltip";
import ManageTags from "../../screens/manage-tags";

View File

@@ -23,6 +23,7 @@ import dayjs from "dayjs";
import React, { useEffect, useState } from "react";
import {
Linking,
Platform,
ScrollView,
Text,
TouchableOpacity,
@@ -31,7 +32,6 @@ import {
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { WebView } from "react-native-webview";
import { db } from "../../../common/database";
import usePricingPlans from "../../../hooks/use-pricing-plans";
import { ToastManager } from "../../../services/event-manager";
@@ -211,16 +211,19 @@ export const BuyPlan = (props: {
? strings["5yearPlanConditions"]()
: [
strings.trialPlanConditions[0](
billingDuration?.duration as number
billingDuration?.duration as number as never
),
strings.trialPlanConditions[1](0)
...(isGithubRelease
? []
: [strings.trialPlanConditions[1](Platform.OS as never)])
]
).map((item) => (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10
gap: 10,
flex: 1
}}
key={item}
>
@@ -229,7 +232,13 @@ export const BuyPlan = (props: {
size={AppFontSize.lg}
name="check"
/>
<Paragraph>{item}</Paragraph>
<Paragraph
style={{
flexShrink: 1
}}
>
{item}
</Paragraph>
</View>
))}
</View>
@@ -243,8 +252,8 @@ export const BuyPlan = (props: {
is5YearPlanSelected
? strings.purchase()
: pricingPlans?.userCanRequestTrial
? strings.subscribeAndStartTrial()
: strings.subscribe()
? strings.subscribeAndStartTrial()
: strings.subscribe()
}
onPress={async () => {
if (isGithubRelease) {
@@ -359,26 +368,42 @@ const ProductItem = (props: {
(product as Plan)?.id);
useEffect(() => {
props.pricingPlans
?.getRegionalDiscount(
props.pricingPlans.currentPlan?.id as string,
props.pricingPlans.isGithubRelease
? ((product as Plan)?.period as string)
: props.productId
)
.then((value) => {
if (
value &&
value.sku?.startsWith(
(props.pricingPlans.selectedProduct as RNIap.Subscription)
?.productId
)
) {
props.pricingPlans.selectProduct(value?.sku as string);
}
setRegionaDiscount(value);
});
}, []);
if (product) {
props.pricingPlans
?.getRegionalDiscount(
props.pricingPlans.currentPlan?.id as string,
props.pricingPlans.isGithubRelease
? ((product as Plan)?.period as string)
: props.productId
)
.then((value) => {
if (
value &&
value.sku?.startsWith(
(props.pricingPlans.selectedProduct as RNIap.Subscription)
?.productId
)
) {
props.pricingPlans.selectProduct(value?.sku as string);
}
setRegionaDiscount(value);
});
}
}, [product, props.pricingPlans, props.productId]);
const discountValue =
(isAnnual && !isGithubRelease) ||
(isGithubRelease && (product as Plan)?.discount?.amount)
? regionalDiscount
? regionalDiscount.discount
: isGithubRelease
? (product as Plan).discount?.amount
: props.pricingPlans.compareProductPrice(
props.pricingPlans.currentPlan?.id as string,
`notesnook.${props.pricingPlans.currentPlan?.id}.yearly`,
`notesnook.${props.pricingPlans.currentPlan?.id}.monthly`
)
: undefined;
return (
<TouchableOpacity
@@ -420,12 +445,11 @@ const ProductItem = (props: {
{isAnnual
? strings.yearly()
: is5YearProduct
? strings.fiveYearPlan()
: strings.monthly()}
? strings.fiveYearPlan()
: strings.monthly()}
</Heading>
{(isAnnual && !isGithubRelease) ||
(isGithubRelease && (product as Plan)?.discount?.amount) ? (
{discountValue ? (
<View
style={{
backgroundColor: colors.static.red,
@@ -436,18 +460,7 @@ const ProductItem = (props: {
}}
>
<Heading color={colors.static.white} size={AppFontSize.xs}>
{strings.bestValue()} -{" "}
{strings.percentOff(
(regionalDiscount
? regionalDiscount.discount
: isGithubRelease
? (product as Plan).discount?.amount
: props.pricingPlans.compareProductPrice(
props.pricingPlans.currentPlan?.id as string,
`notesnook.${props.pricingPlans.currentPlan?.id}.yearly`,
`notesnook.${props.pricingPlans.currentPlan?.id}.monthly`
)) as string
)}
{strings.bestValue()} - {strings.percentOff(`${discountValue}`)}
</Heading>
</View>
) : null}

View File

@@ -21,7 +21,6 @@ import React from "react";
import { View } from "react-native";
import FileViewer from "react-native-file-viewer";
import { ToastManager } from "../../../services/event-manager";
import { AppFontSize } from "../../../utils/size";
import { Button } from "../../ui/button";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";

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 { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";

View File

@@ -1,3 +1,22 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { FeatureId, FeatureResult } from "@notesnook/common";
import { SubscriptionPlan, SubscriptionProvider } from "@notesnook/core";
import { strings } from "@notesnook/intl";

View File

@@ -1,3 +1,22 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import {
FeatureUsage,
formatBytes,
@@ -76,10 +95,10 @@ export function PlanLimits() {
{item.total === Infinity
? strings.unlimited()
: item.id === "storage"
? `${formatBytes(item.used)}/${formatBytes(
item.total
)} ${strings.used()}`
: `${item.used}/${item.total} ${strings.used()}`}
? `${formatBytes(item.used)}/${formatBytes(
item.total
)} ${strings.used()}`
: `${item.used}/${item.total} ${strings.used()}`}
</Paragraph>
</View>
</View>

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 { hosts, Monograph, Note } from "@notesnook/core";
import { hosts, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import Clipboard from "@react-native-clipboard/clipboard";
@@ -28,7 +28,6 @@ import {
TouchableOpacity,
View
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
//@ts-ignore
import ToggleSwitch from "toggle-switch-react-native";
import { db } from "../../../common/database";
@@ -50,8 +49,8 @@ import Input from "../../ui/input";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useAsync } from "react-async-hook";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import { eMenuItemUpdate } from "../../../utils/events";
import { useIsFeatureAvailable } from "@notesnook/common";
async function fetchMonographData(noteId: string) {
const monographId = db.monographs.monograph(noteId);

View File

@@ -79,6 +79,7 @@ type SimpleTabViewProps = {
const createSceneMap = (
scenes: Record<string, React.ComponentType<any>>
): ((props: { route: SimpleRoute }) => React.ReactNode) => {
// eslint-disable-next-line react/display-name
return ({ route }: { route: SimpleRoute }) => {
const SceneComponent = scenes[route.key];
if (!SceneComponent) return null;
@@ -136,8 +137,8 @@ const SimpleTabView = ({
{loadedKeysRef.current.has(route.key)
? getSceneForRoute(route)
: navigationState.index === routeIndex
? getSceneForRoute(route)
: null}
? getSceneForRoute(route)
: null}
</View>
))}
</View>
@@ -432,12 +433,12 @@ const TabBar = (props: SimpleTabBarProps) => {
name="plus"
testID="sidebar-add-button"
size={AppFontSize.lg - 2}
top={10}
color={colors.primary.icon}
onPress={async () => {
if (props.navigationState.index === 1) {
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
const notebooksFeature =
await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
PaywallSheet.present(notebooksFeature);
return;
@@ -485,6 +486,7 @@ const TabBar = (props: SimpleTabBarProps) => {
? "sort-ascending"
: "sort-descending"
}
top={10}
testID="sidebar-sort-button"
color={colors.primary.icon}
onPress={() => {
@@ -520,6 +522,7 @@ const TabBar = (props: SimpleTabBarProps) => {
width: 28,
height: 28
}}
top={10}
testID="sidebar-theme-button"
color={colors.primary.icon}
name={isDark ? "weather-night" : "weather-sunny"}

View File

@@ -24,7 +24,8 @@ import { StoreApi, UseBoundStore } from "zustand";
import { useTotalNotes } from "../../hooks/use-db-item";
import {
eSubscribeEvent,
eUnSubscribeEvent
eUnSubscribeEvent,
ToastManager
} from "../../services/event-manager";
import { TreeItem } from "../../stores/create-notebook-tree-stores";
import { SelectionStore } from "../../stores/item-selection-store";
@@ -103,7 +104,8 @@ export const NotebookItem = ({
? 15 * item.depth
: 15 * 5,
width: "100%",
marginTop: 2
marginTop: 2,
opacity: item.disabled ? 0.5 : 1
}}
>
<Pressable

View File

@@ -76,7 +76,7 @@ export const PinnedSection = React.memo(
onPress: onPress,
onLongPress: onLongPress
})) as SideMenuItem[],
[menuPins, onPress]
[menuPins, onLongPress, onPress]
);
const renderItem = React.useCallback(({ item }: { item: SideMenuItem }) => {

View File

@@ -120,10 +120,9 @@ export const Toast = ({ context = "global" }) => {
alignItems: "center",
alignSelf: "center",
bottom:
Platform.OS === "android"
? Math.max(insets.bottom, 40)
: Math.max(insets.bottom, 40) +
(keyboard.keyboardShown ? keyboard.keyboardHeight : 0),
insets.bottom +
15 +
(keyboard.keyboardShown ? Math.max(0, keyboard.keyboardHeight) : 0),
position: "absolute",
zIndex: 999,
elevation: 15
@@ -158,22 +157,22 @@ export const Toast = ({ context = "global" }) => {
toastOptions.icon
? toastOptions.icon
: toastOptions.type === "success"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
}
size={isFullToastMessage ? AppFontSize.xxxl : AppFontSize.xl}
color={
toastOptions?.icon
? toastOptions?.icon
: toastOptions.type === "error"
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
}
/>

View File

@@ -83,12 +83,13 @@ const SheetWrapper = ({
: 0
};
}, [
colors.primary.background,
colors.primary.border,
largeTablet,
smallTablet,
width,
insets.bottom
colors.primary.background,
colors.primary.border,
bottomInsets,
isGestureNavigationEnabled
]);
const _onOpen = () => {

View File

@@ -17,33 +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 { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Linking, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import {
COMMUNITY_SVG,
LAUNCH_ROCKET,
SUPPORT_SVG,
WELCOME_SVG
} from "../../assets/images/assets";
import useRotator from "../../hooks/use-rotator";
import { eSendEvent } from "../../services/event-manager";
import { getContainerBorder } from "../../utils/colors";
import { getElevationStyle } from "../../utils/elevation";
import { eOpenAddNotebookDialog } from "../../utils/events";
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import { Button } from "../ui/button";
import Seperator from "../ui/seperator";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
import { WELCOME_SVG } from "../../assets/images/assets";
import { useUserStore } from "../../stores/use-user-store";
import { planToId, SubscriptionPlan } from "@notesnook/core";
import { planToDisplayName } from "../../utils/constants";
import AppIcon from "../ui/AppIcon";
import { SvgView } from "../ui/svg";
export type TStep = {
text?: string;

View File

@@ -33,7 +33,7 @@ import { useThemeColors } from "@notesnook/theme";
import { DisplayedNotification } from "@notifee/react-native";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useRef, useState } from "react";
import { InteractionManager, Platform, View } from "react-native";
import { InteractionManager, Platform } from "react-native";
import Share from "react-native-share";
import { DatabaseLogger, db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
@@ -72,8 +72,8 @@ import { useUserStore } from "../stores/use-user-store";
import { eCloseSheet, eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import DatePickerComponent from "../components/date-picker";
@@ -196,7 +196,7 @@ export const useActions = ({
"expiringNotes"
]);
const [item, setItem] = useState(propItem);
const { colors, isDark } = useThemeColors();
const { colors } = useThemeColors();
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(item.id)
@@ -1286,7 +1286,10 @@ export const useActions = ({
(item as Note).headline || (item as Notebook).description || "",
(item as Color).colorCode
);
} catch (e) {}
} catch (e) {
/**
empty */
}
}
});
}

View File

@@ -352,7 +352,10 @@ async function checkForShareExtensionLaunchedInBackground() {
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
MMKV.removeItem("shareExtensionOpened");
}
} catch (e) {}
} catch (e) {
/**
empty */
}
}
const onSuccessfulSubscription = async (

View File

@@ -135,7 +135,7 @@ export const useNoteLocked = (noteId: string | undefined) => {
//@ts-ignore
!globalThis["IS_SHARE_EXTENSION"]
) {
let unsub = useSettingStore.subscribe((state) => {
const unsub = useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
unsub();
db.vaults

View File

@@ -1,15 +1,28 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useAreFeaturesAvailable } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { useEffect } from "react";
import { db } from "../common/database";
import { presentDialog } from "../components/dialog/functions";
import { useDragState } from "../screens/settings/editor/state";
import { eSendEvent } from "../services/event-manager";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
import SettingsService from "../services/settings";
import { useUserStore } from "../stores/use-user-store";
import { eCloseSimpleDialog } from "../utils/events";
export default function useFeatureManager() {
const features = useAreFeaturesAvailable([
@@ -69,7 +82,7 @@ export default function useFeatureManager() {
db.settings.setDefaultTag(undefined);
}
}
}, [features, plan]);
}, [features, plan, user]);
return true;
}

View File

@@ -255,12 +255,15 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
const products = WebPlanCache || (await db.pricing.products());
WebPlanCache = products;
setWebPricingPlans(products);
} catch (e) {}
} catch (e) {
/**
empty */
}
}
setLoadingPlans(false);
};
loadPlans();
}, [options?.promoOffer, cancelPromo]);
}, [options?.promoOffer, cancelPromo, hasTrialOffer]);
function getLocalizedPrice(
product: RNIap.Subscription | RNIap.Product | Plan
@@ -276,8 +279,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? (product as Plan).price.gross
: (product as Plan).period === "5-year"
? (product as Plan).price.gross
: (product as Plan).price.gross
? (product as Plan).price.gross
: (product as Plan).price.gross
}`;
}
@@ -432,8 +435,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
return period.endsWith("W")
? "week"
: period.endsWith("M")
? "month"
: "year";
? "month"
: "year";
} else {
const unit = (product as RNIap.SubscriptionIOS)
?.subscriptionPeriodUnitIOS;
@@ -475,8 +478,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
type: phase.billingPeriod.endsWith("W")
? "week"
: phase.billingPeriod.endsWith("M")
? "month"
: "year"
? "month"
: "year"
};
} else {
const productIos = product as RNIap.SubscriptionIOS;
@@ -530,14 +533,15 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
const formattedPrice = numberWithCommas(monthlyPrice.toFixed(2));
return isAtLeft
? `${symbol} ${formattedPrice}`
: `${formattedPrice} ${symbol}`;
? `${symbol}${formattedPrice}`
: `${formattedPrice}${symbol}`;
};
const getDiscountValue = (p1: string, p2: string, splitToMonth?: boolean) => {
let price1 = Platform.OS === "ios" ? parseInt(p1) : parseInt(p1) / 1000000;
let price1 =
Platform.OS === "ios" ? parseFloat(p1) : parseFloat(p1) / 1000000;
const price2 =
Platform.OS === "ios" ? parseInt(p2) : parseInt(p2) / 1000000;
Platform.OS === "ios" ? parseFloat(p2) : parseFloat(p2) / 1000000;
price1 = splitToMonth ? price1 / 12 : price1;
@@ -587,7 +591,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
} else {
priceValue = price / 1000000;
}
const priceSymbol = localizedPrice.replace(/[\s\d,.]+/, "");
const priceSymbol = localizedPrice.replace(/[\d,.]+/, "");
return { priceValue, priceSymbol, localizedPrice };
};
@@ -607,8 +611,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? ((product as Plan).price.gross / 12).toFixed(2)
: (product as Plan).period === "5-year"
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
}`;
}

View File

@@ -139,7 +139,7 @@ export const FluidPanelsView = React.memo(
if (deviceMode === "smallTablet") {
fluidTabsRef.current?.openDrawer(false);
}
}, [deviceMode, dimensions.width, setFullscreen]);
}, [deviceMode, setFullscreen]);
const closeFullScreenEditor = useCallback(
(current: string) => {
@@ -158,7 +158,7 @@ export const FluidPanelsView = React.memo(
fluidTabsRef.current?.goToIndex(2, false);
}
},
[deviceMode, dimensions.width, setFullscreen]
[deviceMode, setFullscreen]
);
const toggleView = useCallback(
@@ -215,7 +215,7 @@ export const FluidPanelsView = React.memo(
}
}, 0);
},
[deviceMode, fullscreen, setDeviceModeState]
[fullscreen, setDeviceModeState]
);
const checkDeviceType = React.useCallback(
@@ -229,14 +229,14 @@ export const FluidPanelsView = React.memo(
: "mobile";
setDeviceMode(nextDeviceMode, size);
},
[orientation, setDeviceMode, setDimensions]
[orientation, setDeviceMode]
);
useEffect(() => {
if (orientation !== "UNKNOWN") {
checkDeviceType(dimensions);
}
}, [orientation, dimensions]);
}, [orientation, dimensions, checkDeviceType]);
const _onLayout = React.useCallback(
(event: LayoutChangeEvent) => {
@@ -251,13 +251,7 @@ export const FluidPanelsView = React.memo(
setOrientation(OrientationType["PORTRAIT"]);
}
},
[
checkDeviceType,
deviceMode,
dimensions.width,
orientation,
setDeviceMode
]
[setDimensions]
);
const PANE_OFFSET = useMemo(

View File

@@ -16,7 +16,6 @@ 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 { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

View File

@@ -400,17 +400,45 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
recurringMode === RecurringModes.Year
? null
: recurringMode === RecurringModes.Week
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
(item) => (
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
(item) => (
<Button
key={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(item) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(item) > -1) {
days.splice(days.indexOf(item), 1);
return [...days];
}
days.push(item);
return [...days];
});
}}
/>
)
)
: MonthDays.map((item, index) => (
<Button
key={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
item as keyof typeof strings.weekDayNamesShort
]()}
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(item) > -1
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
@@ -422,44 +450,16 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(item) > -1) {
days.splice(days.indexOf(item), 1);
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
}
days.push(item);
days.push(index + 1);
return [...days];
});
}}
/>
)
)
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
}
days.push(index + 1);
return [...days];
});
}}
/>
))}
))}
</ScrollView>
</View>
) : null}
@@ -542,24 +542,24 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
dayjs(date).format("hh:mm A")
)
: recurringMode === RecurringModes.Year
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
</Paragraph>
</>
</View>
@@ -587,8 +587,8 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
? "vibrate"
: "volume-high"
}
fontSize={AppFontSize.xs}
height={35}
@@ -636,6 +636,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
<Heading size={AppFontSize.md}>{strings.referencedIn()}</Heading>
{referencedNotes.value.map((item) => (
<Pressable
key={item.id}
style={{
justifyContent: "space-between",
flexDirection: "row",

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import { isFeatureAvailable } from "@notesnook/common";
import { isImage } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import {
DocumentPickerOptions,
@@ -34,6 +33,7 @@ import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
import { DatabaseLogger, db } from "../../../common/database";
import filesystem from "../../../common/filesystem";
import { compressToFile } from "../../../common/filesystem/compress";
import { santizeUri } from "../../../common/filesystem/utils";
import AttachImage from "../../../components/dialogs/attach-image-dialog";
import { ToastManager } from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
@@ -41,7 +41,6 @@ import { useSettingStore } from "../../../stores/use-setting-store";
import { useUserStore } from "../../../stores/use-user-store";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { santizeUri } from "../../../common/filesystem/utils";
type PickerOptions = {
noteId?: string;
@@ -103,7 +102,7 @@ const file = async (fileOptions: PickerOptions) => {
return;
}
let uri = santizeUri(fileCopyUri.localUri);
const uri = santizeUri(fileCopyUri.localUri);
const hash = await Sodium.hashFile({
uri: uri,
type: "url"

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import type { ToolbarGroupDefinition } from "@notesnook/editor";
import { useEditor } from "./use-editor";
import { FeatureId, FeatureResult } from "@notesnook/common";
import { DayFormat } from "@notesnook/core";
export type useEditorType = ReturnType<typeof useEditor>;

View File

@@ -207,7 +207,7 @@ export const useEditorEvents = (
return () => {
subscriptions.forEach((subscription) => subscription.remove());
};
}, [editor.commands, editor.postMessage]);
}, [editor, editor.commands, editor.postMessage]);
useEffect(() => {
if (loading) return;
if (typeof defaultFontFamily === "object") {
@@ -764,7 +764,7 @@ export const useEditorEvents = (
let filePath: string;
let fileName: string;
if (Platform.OS === "android") {
let file = await ScopedStorage.createDocument(
const file = await ScopedStorage.createDocument(
"table.csv",
"text/csv",
csv,

View File

@@ -38,15 +38,14 @@ import { strings } from "@notesnook/intl";
import { useThemeEngineStore } from "@notesnook/theme";
import { Mutex } from "async-mutex";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Linking } from "react-native";
import WebView from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
eSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { NotePreviewWidget } from "../../../services/note-preview-widget";
@@ -70,7 +69,6 @@ import { SessionHistory } from "./session-history";
import { EditorState, SavePayload } from "./types";
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
import { defaultState, isContentInvalid, isEditorLoaded, post } from "./utils";
import { Linking } from "react-native";
const loadNoteMutex = new Mutex();

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { parseInternalLink } from "@notesnook/core";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { createRef, MutableRefObject, RefObject } from "react";
import { TextInput } from "react-native";
import WebView from "react-native-webview";
@@ -28,10 +29,8 @@ import {
eUnSubscribeEvent
} from "../../../services/event-manager";
import { eOnLoadNote } from "../../../utils/events";
import { NotesnookModule } from "../../../utils/notesnook-module";
import { AppState, EditorState, useEditorType } from "./types";
import { EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
export const textInput = createRef<TextInput>();
export const editorController =

View File

@@ -83,7 +83,10 @@ const useTagItemSelection = createItemSelectionStore(true);
const ManageTags = (props: NavigationProps<"ManageTags">) => {
const { colors } = useThemeColors();
const ids = props.route.params.ids || [];
const ids = React.useMemo(
() => props.route.params.ids || [],
[props.route.params.ids]
);
const [tags, setTags] = useState<Tag[]>();
const [query, setQuery] = useState<string>();
const inputRef = useRef<TextInput>(null);
@@ -124,7 +127,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
tags.splice(0, 0, ...noteTags);
setTags(tags);
},
[]
[ids]
);
const refreshTags = useCallback(() => {
@@ -138,7 +141,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
.sorted(db.settings.getGroupOptions("tags"))
.then(sortAndSetTags);
}
}, [query]);
}, [query, sortAndSetTags]);
useEffect(() => {
refreshTags();

View File

@@ -46,6 +46,7 @@ import {
import {
checkParentSelected,
findRootNotebookId,
getAllNotebookChildren,
getParentNotebookId
} from "../../utils/notebooks";
import { DefaultAppStyles } from "../../utils/styles";
@@ -124,17 +125,25 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
useEffect(() => {
async function filterNotebooks() {
const excludedItems: string[] = [];
const disabledItems: string[] = [];
for (const notebook of selectedNotebooks) {
const children = await getAllNotebookChildren(notebook.id, []);
excludedItems.push(...children);
const parent = await getParentNotebookId(notebook.id);
if (!excludedItems.includes(parent)) {
excludedItems.push(parent);
if (!disabledItems.includes(parent)) {
disabledItems.push(parent);
}
excludedItems.push(notebook.id);
}
const filtered = tree.filter(
(treeItem) => !excludedItems.includes(treeItem.notebook.id)
// Exclude and disable items as needed
const filtered = tree.filter(item => !excludedItems.includes(item.notebook.id)).map(
(treeItem) => {
return {
...treeItem,
disabled: disabledItems.includes(treeItem.notebook.id)
}
}
);
return filtered;
}
filterNotebooks().then((filtered) => {
@@ -149,6 +158,13 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
index={index}
item={item}
onPress={async () => {
if (item.disabled) {
ToastManager.show({
type: "info",
"message": "You cannot move the selected notebook(s) here"
})
return;
}
const selectedNotebook = item.notebook;
presentDialog({
title: strings.moveNotebooks(selectedNotebooks.length),
@@ -239,6 +255,7 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
updateNotebooks();
}, 300);
}}
testID="move-notebook-search"
button={{
icon: "plus",
onPress: async () => {

View File

@@ -202,7 +202,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
) : undefined
}
placeholder={{
title: notebook?.title!,
title: notebook?.title || "",
paragraph: strings.notesEmpty(),
button: strings.addFirstNote(),
action: openEditor,

View File

@@ -17,22 +17,22 @@ 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 { isFeatureAvailable } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { StyleSheet, View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
import PaywallSheet from "../../../components/sheets/paywall";
import { Button } from "../../../components/ui/button";
import { Notice } from "../../../components/ui/notice";
import Paragraph from "../../../components/ui/typography/paragraph";
import { useThemeColors } from "@notesnook/theme";
import { ToastManager } from "../../../services/event-manager";
import { AppFontSize } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { Group } from "./group";
import { DragState, useDragState } from "./state";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import { isFeatureAvailable } from "@notesnook/common";
import { ToastManager } from "../../../services/event-manager";
import PaywallSheet from "../../../components/sheets/paywall";
export const ConfigureToolbar = () => {
const data = useDragState((state) => state.data);
const preset = useDragState((state) => state.preset);

View File

@@ -21,7 +21,7 @@ import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React from "react";
import { View } from "react-native";
import { KeyboardAwareFlatList } from "react-native-keyboard-aware-scroll-view";
import Animated, { FadeInDown } from "react-native-reanimated";
import Animated from "react-native-reanimated";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";

View File

@@ -1,3 +1,21 @@
/*
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 { presentDialog } from "../../components/dialog/functions";
import { DatabaseLogger, db } from "../../common/database";

View File

@@ -1,3 +1,22 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { CirclePartner, SubscriptionStatus } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
@@ -77,7 +96,7 @@ export const NotesnookCircle = () => {
) : null}
{partners.result?.map((item) => (
<Partner item={item} available={!isFree && !isOnTrial} />
<Partner key={item.id} item={item} available={!isFree && !isOnTrial} />
))}
</ScrollView>
);

View File

@@ -25,12 +25,10 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { Dialog } from "../../../components/dialog";
import { Pressable } from "../../../components/ui/pressable";
import Paragraph from "../../../components/ui/typography/paragraph";
import PremiumService from "../../../services/premium";
import { getColorLinearShade } from "../../../utils/colors";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import { verifyUser } from "../functions";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { verifyUser } from "../functions";
interface PickerOptions<T> {
getValue: () => T;

View File

@@ -24,13 +24,7 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { keepLocalCopy, pick } from "@react-native-documents/picker";
import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
FlatList,
Platform,
ScrollView,
View
} from "react-native";
import { ActivityIndicator, Platform, ScrollView, View } from "react-native";
import RNFetchBlob, { ReactNativeBlobUtilStat } from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";

View File

@@ -46,8 +46,10 @@ import { RouteParams, SettingSection } from "./types";
const _SectionItem = ({ item }: { item: SettingSection }) => {
const { colors } = useThemeColors();
// eslint-disable-next-line react-hooks/exhaustive-deps
const isFeatureAvailable = item.featureId
? useIsFeatureAvailable(item.featureId)
? // eslint-disable-next-line react-hooks/rules-of-hooks
useIsFeatureAvailable(item.featureId)
: ({
isAllowed: true
} as FeatureResult);

View File

@@ -16,7 +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 { strings } from "@notesnook/intl";
import { Platform } from "react-native";
import { AuthMode } from "../components/auth/common";
@@ -146,7 +146,7 @@ const updateAvailableMessage = (
icon: "update",
type: "normal",
id: "app-update"
} as Message);
}) as Message;
export function setUpdateAvailableMessage(
version: GithubVersionInfo | CheckVersionResponse

View File

@@ -33,7 +33,7 @@ let subs: RNIap.Subscription[] = [];
let products: RNIap.Product[] = [];
async function setPremiumStatus() {
let userstore = useUserStore.getState();
const userstore = useUserStore.getState();
try {
const user = await db.user.getUser();
if (!user) {
@@ -99,7 +99,7 @@ const showVerifyEmailDialog = () => {
paragraph: strings.emailConfirmationLinkSent(),
action: async () => {
try {
let lastVerificationEmailTime =
const lastVerificationEmailTime =
SettingsService.get().lastVerificationEmailTime;
if (
lastVerificationEmailTime &&
@@ -148,7 +148,7 @@ const subscriptions = {
*/
get: () => {
if (Platform.OS === "android") return;
let _subscriptions = MMKV.getString("subscriptionsIOS");
const _subscriptions = MMKV.getString("subscriptionsIOS");
if (!_subscriptions) return [];
return JSON.parse(_subscriptions) as (
| RNIap.SubscriptionPurchase
@@ -167,7 +167,7 @@ const subscriptions = {
const _subscriptions = subscriptions.get();
if (!_subscriptions) return;
let index = _subscriptions.findIndex(
const index = _subscriptions.findIndex(
(s) => s.transactionId === subscription.transactionId
);
if (index === -1) {
@@ -182,7 +182,7 @@ const subscriptions = {
const _subscriptions = subscriptions.get();
if (!_subscriptions) return;
let index = _subscriptions.findIndex(
const index = _subscriptions.findIndex(
(s) => s.transactionId === transactionId
);
if (index !== -1) {
@@ -201,9 +201,9 @@ const subscriptions = {
if (subscription.transactionReceipt) {
if (Platform.OS === "ios") {
let user = await db.user.getUser();
const user = await db.user.getUser();
if (!user) return;
let requestData = {
const requestData = {
method: "POST",
body: JSON.stringify({
receipt_data: subscription.transactionReceipt,
@@ -216,14 +216,14 @@ const subscriptions = {
};
try {
let result = await fetch(
const result = await fetch(
__DEV__
? "https://payments.streetwriters.co/apple/verify"
: "https://payments.streetwriters.co/apple/verify",
requestData
);
let text = await result.text();
const text = await result.text();
if (!result.ok) {
if (text === "Receipt already expired.") {
@@ -243,7 +243,7 @@ const subscriptions = {
_subscription?: RNIap.SubscriptionPurchase | RNIap.ProductPurchase
) => {
if (Platform.OS === "android") return;
let _subscriptions = subscriptions.get();
const _subscriptions = subscriptions.get();
if (!_subscriptions) return;
let subscription = null;
if (_subscription) {

View File

@@ -22,7 +22,6 @@ import { db } from "../common/database";
import { DatabaseLogger } from "../common/database/index";
import { initAfterSync } from "../stores/index";
import { SyncStatus, useUserStore } from "../stores/use-user-store";
import BackgroundSync from "./background-sync";
import { ToastManager } from "./event-manager";
import SettingsService from "./settings";

View File

@@ -196,7 +196,7 @@ export const Editor = ({
}
};
}
}, []);
}, [editor.ref, editorRef]);
return (
<View

View File

@@ -78,7 +78,7 @@ const getLinkPreview = (url: string) => {
};
async function sanitizeHtml(site: string, title?: string) {
try {
let html = await fetchHandle.current?.processUrl(site);
const html = await fetchHandle.current?.processUrl(site);
return (
html +
"<hr/>" +
@@ -112,7 +112,7 @@ type DefaultNote = {
};
};
let defaultNote: DefaultNote = {
const defaultNote: DefaultNote = {
title: "",
id: undefined,
content: {
@@ -174,7 +174,8 @@ const ShareView = () => {
const [compress, setCompress] = useState(true);
globalThis["IS_SHARE_EXTENSION"] = true;
const onKeyboardDidShow = (event: KeyboardEvent) => {
let height = Dimensions.get("screen").height - event.endCoordinates.screenY;
const height =
Dimensions.get("screen").height - event.endCoordinates.screenY;
if (height < 100) {
setKeyboardHeight(0);
keyboardHeightRef.current = 0;
@@ -190,11 +191,11 @@ const ShareView = () => {
};
useEffect(() => {
let keyboardDidShow = Keyboard.addListener(
const keyboardDidShow = Keyboard.addListener(
"keyboardDidShow",
onKeyboardDidShow
);
let keyboardDidHide = Keyboard.addListener(
const keyboardDidHide = Keyboard.addListener(
"keyboardDidHide",
onKeyboardDidHide
);
@@ -211,10 +212,10 @@ const ShareView = () => {
}, [fullQualityImages]);
const showLinkPreview = async (note: DefaultNote, link: string) => {
let _note = note;
const _note = note;
_note.content.data = makeHtmlFromUrl(link);
try {
let preview = await getLinkPreview(link);
const preview = await getLinkPreview(link);
_note.title = preview.title;
} catch (e) {
console.log(e);
@@ -260,7 +261,7 @@ const ShareView = () => {
}
let note = { ...defaultNote };
for (let item of data) {
for (const item of data) {
if (item.type === "text") {
setRawData(item);
if (isURL(item.value)) {
@@ -357,7 +358,7 @@ const ShareView = () => {
return;
}
let rawContent = note.contentId
const rawContent = note.contentId
? await db.content.get(note.contentId)
: null;
noteData = {
@@ -406,7 +407,7 @@ const ShareView = () => {
if (value === 2) {
setLoadingPage(true);
setTimeout(async () => {
let html = await sanitizeHtml(rawData?.value || "", note.title);
const html = await sanitizeHtml(rawData?.value || "", note.title);
noteContent.current = html || "";
setLoadingPage(false);
onLoad();
@@ -417,11 +418,11 @@ const ShareView = () => {
}, 300);
} else {
setLoadingPage(false);
let html = !rawData.value
const html = !rawData.value
? ""
: isURL(rawData?.value)
? makeHtmlFromUrl(rawData?.value)
: makeHtmlFromPlainText(rawData?.value);
? makeHtmlFromUrl(rawData?.value)
: makeHtmlFromPlainText(rawData?.value);
setNote((note) => {
note.content.data = html;
noteContent.current = html;

View File

@@ -43,7 +43,7 @@ const StorageKeys = {
appSettings: "appSettings"
};
let appSettingsJson = MMKV.getString(StorageKeys.appSettings);
const appSettingsJson = MMKV.getString(StorageKeys.appSettings);
let appSettings: SettingStore["settings"] | null = null;
if (appSettingsJson) {
appSettings = JSON.parse(appSettingsJson) as SettingStore["settings"];
@@ -86,9 +86,9 @@ export const useShareStore = create<ShareStore>((set) => ({
set({ appendNote: noteId });
},
restore: () => {
let appendNote = MMKV.getString(StorageKeys.appendNote);
let selectedNotebooks = MMKV.getString(StorageKeys.selectedNotebooks);
let selectedTags = MMKV.getString(StorageKeys.selectedTag);
const appendNote = MMKV.getString(StorageKeys.appendNote);
const selectedNotebooks = MMKV.getString(StorageKeys.selectedNotebooks);
const selectedTags = MMKV.getString(StorageKeys.selectedTag);
set({
appendNote: appendNote,
selectedNotebooks: selectedNotebooks ? JSON.parse(selectedNotebooks) : [],

View File

@@ -28,6 +28,7 @@ export type TreeItem = {
notebook: Notebook;
depth: number;
hasChildren: boolean;
disabled?: boolean;
};
function removeTreeItem(tree: TreeItem[], id: string) {

View File

@@ -37,7 +37,7 @@ import { useUserStore } from "./use-user-store";
async function syncShortcuts(result: ShortcutInfo[]) {
try {
for (let shortcut of result) {
for (const shortcut of result) {
switch (shortcut.type) {
case "note":
{

View File

@@ -61,6 +61,25 @@ export async function checkParentSelected(
}
}
export async function getAllNotebookChildren(id: string, childrenIds: string[] = []) {
const relation = await db.relations
.from(
{
id,
type: "notebook"
},
"notebook"
)
.get()
childrenIds.push(...relation.map(rel => rel.toId))
for (let rel of relation) {
await getAllNotebookChildren(rel.toId, childrenIds)
}
return childrenIds
}
export async function getParentNotebookId(id: string) {
const relation = await db.relations
.to(

View File

@@ -38,6 +38,7 @@ export async function unlockVault({
if (unlockPromise) {
return unlockPromise;
}
// eslint-disable-next-line no-async-promise-executor
unlockPromise = new Promise(async (resolve) => {
const result = await (async () => {
if (db.vault.unlocked && !requirePassword) return true;

View File

@@ -68,6 +68,32 @@ describe("NOTEBOOKS", () => {
.run();
});
it("Moving notebook should not be moved to child notebook", async () => {
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Add notebook")
.createNotebook("Sub notebook", true)
.wait(500)
.waitAndTapById("expand-notebook-0")
.isVisibleByText("Sub notebook")
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Move notebook")
.wait(500)
.isNotVisibleByText("Sub notebook")
.typeTextById("move-notebook-search", "Sub notebook")
.wait(500)
.isNotVisibleByText("Sub notebook")
.run();
});
it("Edit notebook", async () => {
await TestBuilder.create()
.prepare()

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,8 @@
"e2e-ios": "detox test -c ios.sim.release --detectOpenHandles",
"bump": "npx react-native bump-version --skip-semver-for android",
"release-android": "cd android && GITHUB_RELEASE=true ENVFILE=.env.public ./gradlew assembleRelease --no-daemon",
"release-android-bundle": "cd android && ./gradlew bundleRelease --no-daemon"
"release-android-bundle": "cd android && ./gradlew bundleRelease --no-daemon",
"lint": "eslint --ext .js,.jsx,.tsx,.ts ./app"
},
"overrides": {
"react-native": "0.82.1",
@@ -162,7 +163,6 @@
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.82.1",
"@react-native/eslint-config": "0.82.1",
"@react-native/metro-config": "0.82.1",
"@react-native/typescript-config": "0.82.1",
"@rspack/core": "^1.5.8",
@@ -183,12 +183,6 @@
"babel-loader": "^10.0.0",
"babel-plugin-syntax-hermes-parser": "^0.32.1",
"detox": "^20.27.6",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"eslint-plugin-unused-imports": "^4.1.4",
"expect": "^29.7.0",
"fonteditor-core": "^2.1.11",
"jest": "^29.7.0",
@@ -208,4 +202,4 @@
"engines": {
"node": ">=20"
}
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.3.14",
"version": "3.3.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.3.14",
"version": "3.3.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.3.14",
"version": "3.3.15",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -22,6 +22,7 @@ import "@react-pdf-viewer/core/lib/styles/index.css";
import { ToolbarSlot, toolbarPlugin } from "@react-pdf-viewer/toolbar";
import { Button, Flex, Text } from "@theme-ui/components";
import { searchPlugin } from "@react-pdf-viewer/search";
import "@react-pdf-viewer/search/lib/styles/index.css";
import {
ChevronDown,
ChevronUp,
@@ -80,12 +81,30 @@ export function PdfPreview(props: PdfPreviewProps) {
".rpv-search__popover label, .rpv-search__popover span": {
fontFamily: "body",
fontSize: "body"
fontSize: "body",
color: "paragraph"
},
".rpv-core__popover-body, .rpv-core__arrow": {
bg: "background",
borderColor: "border"
borderColor: "border",
color: "paragraph"
},
".rpv-search__popover .rpv-core__minimal-button": {
color: "icon",
":hover": {
bg: "hover"
}
},
".rpv-search__popover .rpv-core__button": {
bg: "background-secondary",
color: "paragraph",
border: "none",
":hover": {
bg: "hover"
}
},
".rpv-search__popover-label-checkbox": {
@@ -112,6 +131,10 @@ export function PdfPreview(props: PdfPreviewProps) {
":hover:not(:focus)": {
outline: "1.5px solid var(--accent)"
}
},
".rpv-search__popover .rpv-core__textbox": {
width: "85% !important"
}
}}
>

View File

@@ -1,17 +1,11 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-09-22 09:18+0500\n"
"POT-Creation-Date: 2026-04-20 11:33+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/strings.ts:2411
msgid " \"Notebook > Notes\""
@@ -68,6 +62,11 @@ msgstr "{0} downloaded"
msgid "{0} Highlights 🎉"
msgstr "{0} Highlights 🎉"
#. placeholder {0}: platform === "ios" ? "Apple" : "Google"
#: src/strings.ts:2567
msgid "{0} will remind you before your trial ends"
msgstr "{0} will remind you before your trial ends"
#: src/strings.ts:1753
msgid "{count, plural, one {# error occured} other {# errors occured}}"
msgstr "{count, plural, one {# error occured} other {# errors occured}}"
@@ -623,7 +622,7 @@ msgstr "2FA code is required()"
msgid "2FA code sent via {method}"
msgstr "2FA code sent via {method}"
#: src/strings.ts:2582
#: src/strings.ts:2585
msgid "5 year plan (One time purchase)"
msgstr "5 year plan (One time purchase)"
@@ -735,7 +734,7 @@ msgstr "Add tags to multiple notes at once"
msgid "Add to dictionary"
msgstr "Add to dictionary"
#: src/strings.ts:2615
#: src/strings.ts:2618
msgid "Add to home"
msgstr "Add to home"
@@ -751,7 +750,7 @@ msgstr "Add your first note"
msgid "Add your first notebook"
msgstr "Add your first notebook"
#: src/strings.ts:2618
#: src/strings.ts:2621
msgid "Adjust the line height of the editor"
msgstr "Adjust the line height of the editor"
@@ -843,7 +842,7 @@ msgstr "Amount"
msgid "An error occurred while migrating your data. You can logout of your account and try to relogin. However this is not recommended as it may result in some data loss if your data was not synced."
msgstr "An error occurred while migrating your data. You can logout of your account and try to relogin. However this is not recommended as it may result in some data loss if your data was not synced."
#: src/strings.ts:2576
#: src/strings.ts:2579
msgid "and"
msgstr "and"
@@ -863,7 +862,7 @@ msgstr "and much more."
msgid "and we will manually confirm your account."
msgstr "and we will manually confirm your account."
#: src/strings.ts:2593
#: src/strings.ts:2596
msgid "ANNOUNCEMENT"
msgstr "ANNOUNCEMENT"
@@ -1105,7 +1104,7 @@ msgstr "Available on iOS"
msgid "Available on iOS & Android"
msgstr "Available on iOS & Android"
#: src/strings.ts:2641
#: src/strings.ts:2644
msgid "Back"
msgstr "Back"
@@ -1201,7 +1200,7 @@ msgstr "Behaviour"
msgid "Believer plan"
msgstr "Believer plan"
#: src/strings.ts:2579
#: src/strings.ts:2582
msgid "Best value"
msgstr "Best value"
@@ -1265,7 +1264,7 @@ msgstr "Bullet list"
msgid "By"
msgstr "By"
#: src/strings.ts:2574
#: src/strings.ts:2577
msgid "By joining you agree to our"
msgstr "By joining you agree to our"
@@ -1285,7 +1284,7 @@ msgstr "Can I cancel my free trial anytime?"
msgid "Cancel"
msgstr "Cancel"
#: src/strings.ts:2572
#: src/strings.ts:2575
msgid "Cancel anytime, subscription auto-renews."
msgstr "Cancel anytime, subscription auto-renews."
@@ -1374,7 +1373,7 @@ msgstr "Change notification sound"
msgid "Change password"
msgstr "Change password"
#: src/strings.ts:2586
#: src/strings.ts:2589
msgid "Change plan"
msgstr "Change plan"
@@ -1486,7 +1485,7 @@ msgstr "Choose from pre-built themes or create your own"
msgid "Choose how dates are displayed in the app"
msgstr "Choose how dates are displayed in the app"
#: src/strings.ts:2621
#: src/strings.ts:2624
msgid "Choose how day is displayed in the app"
msgstr "Choose how day is displayed in the app"
@@ -1502,7 +1501,7 @@ msgstr "Choose how time is displayed in the app"
msgid "Choose how you want to secure your notes locally."
msgstr "Choose how you want to secure your notes locally."
#: src/strings.ts:2626
#: src/strings.ts:2629
msgid "Choose what day to display as the first day of the week"
msgstr "Choose what day to display as the first day of the week"
@@ -1600,7 +1599,7 @@ msgstr ""
msgid "Clearing trash will permanently delete all the items in your trash. This action is IRREVERSIBLE."
msgstr "Clearing trash will permanently delete all the items in your trash. This action is IRREVERSIBLE."
#: src/strings.ts:2608
#: src/strings.ts:2611
msgid "Click here to directly claim the promotion."
msgstr "Click here to directly claim the promotion."
@@ -1620,11 +1619,11 @@ msgstr "Click to remove"
msgid "Click to reset {title}"
msgstr "Click to reset {title}"
#: src/strings.ts:2616
#: src/strings.ts:2619
msgid "Click to save"
msgstr "Click to save"
#: src/strings.ts:2612
#: src/strings.ts:2615
msgid "Click to update"
msgstr "Click to update"
@@ -1769,7 +1768,7 @@ msgstr "Confirm password"
msgid "Confirm pin"
msgstr "Confirm pin"
#: src/strings.ts:2640
#: src/strings.ts:2643
msgid "Confirmation email sent"
msgstr "Confirmation email sent"
@@ -2056,7 +2055,7 @@ msgstr "Date uploaded"
msgid "Day"
msgstr "Day"
#: src/strings.ts:2620
#: src/strings.ts:2623
msgid "Day format"
msgstr "Day format"
@@ -2141,7 +2140,7 @@ msgstr "Delete collapsed section"
msgid "Delete column"
msgstr "Delete column"
#: src/strings.ts:2637
#: src/strings.ts:2640
msgid "Delete data"
msgstr "Delete data"
@@ -2398,7 +2397,7 @@ msgstr "Easy access"
msgid "Edit"
msgstr "Edit"
#: src/strings.ts:2627
#: src/strings.ts:2630
msgid "Edit creation date"
msgstr "Edit creation date"
@@ -2428,7 +2427,7 @@ msgstr "Edit your full name"
msgid "Editor"
msgstr "Editor"
#: src/strings.ts:2583
#: src/strings.ts:2586
msgid "Education plan"
msgstr "Education plan"
@@ -2616,7 +2615,7 @@ msgstr "Enter the gift code to redeem your subscription."
msgid "Enter the recovery code to continue logging in"
msgstr "Enter the recovery code to continue logging in"
#: src/strings.ts:2619
#: src/strings.ts:2622
msgid "Enter title"
msgstr "Enter title"
@@ -2712,7 +2711,7 @@ msgstr "Expand sidebar"
msgid "Experience the next level of private note taking\""
msgstr "Experience the next level of private note taking\""
#: src/strings.ts:2633
#: src/strings.ts:2636
msgid "Expiry date"
msgstr "Expiry date"
@@ -2741,7 +2740,7 @@ msgstr "Export all notes as pdf, markdown, html or text in a single zip file"
msgid "Export as{0}"
msgstr "Export as{0}"
#: src/strings.ts:2634
#: src/strings.ts:2637
msgid "Export CSV"
msgstr "Export CSV"
@@ -2769,7 +2768,7 @@ msgstr "Faced an issue or have a suggestion? Click here to create a bug report"
msgid "Failed"
msgstr "Failed"
#: src/strings.ts:2638
#: src/strings.ts:2641
msgid "Failed to attach file"
msgstr "Failed to attach file"
@@ -2906,7 +2905,7 @@ msgstr "Filter attachments by filename, type or hash"
msgid "Filter languages"
msgstr "Filter languages"
#: src/strings.ts:2605
#: src/strings.ts:2608
msgid "Finish your purchase in the browser."
msgstr "Finish your purchase in the browser."
@@ -3096,7 +3095,7 @@ msgstr "Getting recovery codes"
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr "GNU GENERAL PUBLIC LICENSE Version 3"
#: src/strings.ts:2606
#: src/strings.ts:2609
msgid "Go back"
msgstr "Go back"
@@ -3140,10 +3139,6 @@ msgstr "Go to web app"
msgid "Google will remind you 2 days before your trial ends."
msgstr "Google will remind you 2 days before your trial ends."
#: src/strings.ts:2566
msgid "Google will remind you before your trial ends"
msgstr "Google will remind you before your trial ends"
#: src/strings.ts:586
msgid "Got it"
msgstr "Got it"
@@ -3360,7 +3355,7 @@ msgstr "Import & export"
msgid "Import completed"
msgstr "Import completed"
#: src/strings.ts:2635
#: src/strings.ts:2638
msgid "Import CSV"
msgstr "Import CSV"
@@ -3448,7 +3443,7 @@ msgstr "Invalid CORS proxy url"
msgid "Invalid email"
msgstr "Invalid email"
#: src/strings.ts:2643
#: src/strings.ts:2646
msgid "Invalid recovery key. Make sure to input your account recovery key, not a 2FA recovery code."
msgstr "Invalid recovery key. Make sure to input your account recovery key, not a 2FA recovery code."
@@ -3598,7 +3593,7 @@ msgstr "Light"
msgid "Line {line}, Column {column}"
msgstr "Line {line}, Column {column}"
#: src/strings.ts:2617
#: src/strings.ts:2620
msgid "Line height"
msgstr "Line height"
@@ -3771,7 +3766,7 @@ msgstr "Login successful"
msgid "Login to encrypt and sync notes"
msgstr "Login to encrypt and sync notes"
#: src/strings.ts:2610
#: src/strings.ts:2613
msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
msgstr "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
@@ -4169,7 +4164,7 @@ msgstr "No notebooks selected to move"
msgid "No one can view this {type} except you."
msgstr "No one can view this {type} except you."
#: src/strings.ts:2613
#: src/strings.ts:2616
msgid "No password"
msgstr "No password"
@@ -4306,11 +4301,11 @@ msgstr "notes imported"
msgid "Notesnook"
msgstr "Notesnook"
#: src/strings.ts:2598
#: src/strings.ts:2601
msgid "Notesnook Circle"
msgstr "Notesnook Circle"
#: src/strings.ts:2600
#: src/strings.ts:2603
msgid "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom."
msgstr "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom."
@@ -4607,7 +4602,7 @@ msgstr "Pin notification"
msgid "Pinned"
msgstr "Pinned"
#: src/strings.ts:2580
#: src/strings.ts:2583
msgid "Plan limits"
msgstr "Plan limits"
@@ -4871,7 +4866,7 @@ msgstr "Privacy for everyone"
msgid "Privacy mode"
msgstr "Privacy mode"
#: src/strings.ts:2575
#: src/strings.ts:2578
msgid "privacy policy"
msgstr "privacy policy"
@@ -4943,7 +4938,7 @@ msgstr "Publish"
msgid "Publish note"
msgstr "Publish note"
#: src/strings.ts:2614
#: src/strings.ts:2617
msgid "Publish to the web"
msgstr "Publish to the web"
@@ -4967,7 +4962,7 @@ msgstr "Published note can only be viewed by someone with the password."
msgid "Published note link will be automatically deleted once it is viewed by someone."
msgstr "Published note link will be automatically deleted once it is viewed by someone."
#: src/strings.ts:2568
#: src/strings.ts:2571
msgid "Purchase"
msgstr "Purchase"
@@ -5107,7 +5102,7 @@ msgstr "Recovery successful!"
msgid "Redeem"
msgstr "Redeem"
#: src/strings.ts:2597
#: src/strings.ts:2600
msgid "Redeem code"
msgstr "Redeem code"
@@ -5883,7 +5878,7 @@ msgstr "Set as light theme"
msgid "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
msgstr "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
#: src/strings.ts:2631
#: src/strings.ts:2634
msgid "Set expiry"
msgstr "Set expiry"
@@ -6081,7 +6076,7 @@ msgstr "Source code"
msgid "Spaces"
msgstr "Spaces"
#: src/strings.ts:2590
#: src/strings.ts:2593
msgid "Special Offer"
msgstr "Special Offer"
@@ -6161,11 +6156,11 @@ msgstr "Subgroup added"
msgid "Submit"
msgstr "Submit"
#: src/strings.ts:2569
#: src/strings.ts:2572
msgid "Subscribe"
msgstr "Subscribe"
#: src/strings.ts:2570
#: src/strings.ts:2573
msgid "Subscribe and start free trial"
msgstr "Subscribe and start free trial"
@@ -6424,7 +6419,7 @@ msgstr "Terms of service"
msgid "Terms of Service "
msgstr "Terms of Service "
#: src/strings.ts:2577
#: src/strings.ts:2580
msgid "terms of use."
msgstr "terms of use."
@@ -6456,7 +6451,7 @@ msgstr "Thank you for reporting!"
msgid "Thank you for subscribing"
msgstr "Thank you for subscribing"
#: src/strings.ts:2585
#: src/strings.ts:2588
msgid "Thank you for the purchase"
msgstr "Thank you for the purchase"
@@ -6472,7 +6467,7 @@ msgstr "Thank you. You are the proof that privacy always comes first."
msgid "The {title} at {url} is not compatible with this client."
msgstr "The {title} at {url} is not compatible with this client."
#: src/strings.ts:2630
#: src/strings.ts:2633
msgid "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
msgstr "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
@@ -6480,7 +6475,7 @@ msgstr "The incoming note could not be unlocked with the provided password. Ente
msgid "The information above will be publically available at"
msgstr "The information above will be publically available at"
#: src/strings.ts:2604
#: src/strings.ts:2607
msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
msgstr "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
@@ -6552,7 +6547,7 @@ msgstr "This error usually means the database file is either corrupt or it could
msgid "This error usually means the search index is corrupted."
msgstr "This error usually means the search index is corrupted."
#: src/strings.ts:2643
#: src/strings.ts:2647
msgid "This feature is not available on this plan."
msgstr "This feature is not available on this plan."
@@ -6560,7 +6555,7 @@ msgstr "This feature is not available on this plan."
msgid "This image cannot be previewed"
msgstr "This image cannot be previewed"
#: src/strings.ts:2571
#: src/strings.ts:2574
msgid "This is a one time purchase, no subscription."
msgstr "This is a one time purchase, no subscription."
@@ -6573,7 +6568,7 @@ msgstr "This may take a while"
msgid "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co."
msgstr "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co."
#: src/strings.ts:2636
#: src/strings.ts:2639
msgid "This note is empty"
msgstr "This note is empty"
@@ -6767,7 +6762,7 @@ msgstr "Undo"
msgid "Unfavorite"
msgstr "Unfavorite"
#: src/strings.ts:2581
#: src/strings.ts:2584
msgid "Unlimited"
msgstr "Unlimited"
@@ -6783,7 +6778,7 @@ msgstr "Unlink notebook"
msgid "Unlock"
msgstr "Unlock"
#: src/strings.ts:2628
#: src/strings.ts:2631
msgid "Unlock incoming note"
msgstr "Unlock incoming note"
@@ -6800,7 +6795,7 @@ msgstr "Unlock note"
msgid "Unlock note to delete it"
msgstr "Unlock note to delete it"
#: src/strings.ts:2639
#: src/strings.ts:2642
msgid "Unlock note to merge conflicts"
msgstr "Unlock note to merge conflicts"
@@ -6852,7 +6847,7 @@ msgstr "Unpublish notes to delete them"
msgid "Unregister"
msgstr "Unregister"
#: src/strings.ts:2632
#: src/strings.ts:2635
msgid "Unset expiry"
msgstr "Unset expiry"
@@ -6901,7 +6896,7 @@ msgstr "Upgrade to Notesnook Pro to create more tags."
msgid "Upgrade to Pro"
msgstr "Upgrade to Pro"
#: src/strings.ts:2596
#: src/strings.ts:2599
msgid "Upgrade to redeem"
msgstr "Upgrade to redeem"
@@ -7049,7 +7044,7 @@ msgstr "Using official Notesnook instance"
msgid "v{version} available"
msgstr "v{version} available"
#: src/strings.ts:2644
#: src/strings.ts:2649
msgid "Value must be between {min} and {max}"
msgstr "Value must be between {min} and {max}"
@@ -7141,7 +7136,7 @@ msgstr "View source code"
msgid "View your recovery codes to recover your account in case you lose access to your two-factor authentication methods."
msgstr "View your recovery codes to recover your account in case you lose access to your two-factor authentication methods."
#: src/strings.ts:2611
#: src/strings.ts:2614
msgid "Views"
msgstr "Views"
@@ -7209,7 +7204,7 @@ msgstr "Wednesday"
msgid "Week"
msgstr "Week"
#: src/strings.ts:2624
#: src/strings.ts:2627
msgid "Week format"
msgstr "Week format"
@@ -7226,7 +7221,7 @@ msgstr "Welcome back, {email}"
msgid "Welcome back!"
msgstr "Welcome back!"
#: src/strings.ts:2584
#: src/strings.ts:2587
msgid "Welcome to Notesnook {plan}"
msgstr "Welcome to Notesnook {plan}"
@@ -7303,7 +7298,7 @@ msgstr "Yes, you can cancel your trial anytime. No questions asked."
msgid "You also agree to recieve marketing emails from us which you can opt-out of from app settings."
msgstr "You also agree to recieve marketing emails from us which you can opt-out of from app settings."
#: src/strings.ts:2589
#: src/strings.ts:2592
msgid "You are already subscribed to this plan."
msgstr "You are already subscribed to this plan."
@@ -7335,7 +7330,7 @@ msgstr "You can also link a note to multiple Notebooks. Tap and hold any noteboo
msgid "You can change the theme at any time from Settings or the side menu."
msgstr "You can change the theme at any time from Settings or the side menu."
#: src/strings.ts:2592
#: src/strings.ts:2595
msgid "You can change your subscription plan from the web app"
msgstr "You can change your subscription plan from the web app"
@@ -7405,7 +7400,7 @@ msgstr "You have been logged out from all other devices."
msgid "You have been logged out."
msgstr "You have been logged out."
#: src/strings.ts:2588
#: src/strings.ts:2591
msgid "You have made a one time purchase. To change your plan please contact support."
msgstr "You have made a one time purchase. To change your plan please contact support."
@@ -7526,7 +7521,7 @@ msgstr "Your changes have been saved and will be reflected after the app has ref
msgid "Your current 2FA method is {method}"
msgstr "Your current 2FA method is {method}"
#: src/strings.ts:2595
#: src/strings.ts:2598
msgid "Your current subscription does not allow changing plans"
msgstr "Your current subscription does not allow changing plans"
@@ -7574,7 +7569,7 @@ msgstr "Your free trial has started"
msgid "Your free trial is ending soon"
msgstr "Your free trial is ending soon"
#: src/strings.ts:2623
#: src/strings.ts:2626
msgid "Your free trial is on-going. Your subscription will start on {trialExpiryDate}"
msgstr "Your free trial is on-going. Your subscription will start on {trialExpiryDate}"

View File

@@ -1,17 +1,11 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-09-22 09:18+0500\n"
"POT-Creation-Date: 2026-04-20 11:33+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: pseudo-LOCALE\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/strings.ts:2411
msgid " \"Notebook > Notes\""
@@ -68,6 +62,11 @@ msgstr ""
msgid "{0} Highlights 🎉"
msgstr ""
#. placeholder {0}: platform === "ios" ? "Apple" : "Google"
#: src/strings.ts:2567
msgid "{0} will remind you before your trial ends"
msgstr ""
#: src/strings.ts:1753
msgid "{count, plural, one {# error occured} other {# errors occured}}"
msgstr ""
@@ -623,7 +622,7 @@ msgstr ""
msgid "2FA code sent via {method}"
msgstr ""
#: src/strings.ts:2582
#: src/strings.ts:2585
msgid "5 year plan (One time purchase)"
msgstr ""
@@ -735,7 +734,7 @@ msgstr ""
msgid "Add to dictionary"
msgstr ""
#: src/strings.ts:2615
#: src/strings.ts:2618
msgid "Add to home"
msgstr ""
@@ -751,7 +750,7 @@ msgstr ""
msgid "Add your first notebook"
msgstr ""
#: src/strings.ts:2618
#: src/strings.ts:2621
msgid "Adjust the line height of the editor"
msgstr ""
@@ -843,7 +842,7 @@ msgstr ""
msgid "An error occurred while migrating your data. You can logout of your account and try to relogin. However this is not recommended as it may result in some data loss if your data was not synced."
msgstr ""
#: src/strings.ts:2576
#: src/strings.ts:2579
msgid "and"
msgstr ""
@@ -863,7 +862,7 @@ msgstr ""
msgid "and we will manually confirm your account."
msgstr ""
#: src/strings.ts:2593
#: src/strings.ts:2596
msgid "ANNOUNCEMENT"
msgstr ""
@@ -1105,7 +1104,7 @@ msgstr ""
msgid "Available on iOS & Android"
msgstr ""
#: src/strings.ts:2641
#: src/strings.ts:2644
msgid "Back"
msgstr ""
@@ -1201,7 +1200,7 @@ msgstr ""
msgid "Believer plan"
msgstr ""
#: src/strings.ts:2579
#: src/strings.ts:2582
msgid "Best value"
msgstr ""
@@ -1265,7 +1264,7 @@ msgstr ""
msgid "By"
msgstr ""
#: src/strings.ts:2574
#: src/strings.ts:2577
msgid "By joining you agree to our"
msgstr ""
@@ -1285,7 +1284,7 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/strings.ts:2572
#: src/strings.ts:2575
msgid "Cancel anytime, subscription auto-renews."
msgstr ""
@@ -1374,7 +1373,7 @@ msgstr ""
msgid "Change password"
msgstr ""
#: src/strings.ts:2586
#: src/strings.ts:2589
msgid "Change plan"
msgstr ""
@@ -1486,7 +1485,7 @@ msgstr ""
msgid "Choose how dates are displayed in the app"
msgstr ""
#: src/strings.ts:2621
#: src/strings.ts:2624
msgid "Choose how day is displayed in the app"
msgstr ""
@@ -1502,7 +1501,7 @@ msgstr ""
msgid "Choose how you want to secure your notes locally."
msgstr ""
#: src/strings.ts:2626
#: src/strings.ts:2629
msgid "Choose what day to display as the first day of the week"
msgstr ""
@@ -1589,7 +1588,7 @@ msgstr ""
msgid "Clearing trash will permanently delete all the items in your trash. This action is IRREVERSIBLE."
msgstr ""
#: src/strings.ts:2608
#: src/strings.ts:2611
msgid "Click here to directly claim the promotion."
msgstr ""
@@ -1609,11 +1608,11 @@ msgstr ""
msgid "Click to reset {title}"
msgstr ""
#: src/strings.ts:2616
#: src/strings.ts:2619
msgid "Click to save"
msgstr ""
#: src/strings.ts:2612
#: src/strings.ts:2615
msgid "Click to update"
msgstr ""
@@ -1758,7 +1757,7 @@ msgstr ""
msgid "Confirm pin"
msgstr ""
#: src/strings.ts:2640
#: src/strings.ts:2643
msgid "Confirmation email sent"
msgstr ""
@@ -2045,7 +2044,7 @@ msgstr ""
msgid "Day"
msgstr ""
#: src/strings.ts:2620
#: src/strings.ts:2623
msgid "Day format"
msgstr ""
@@ -2130,7 +2129,7 @@ msgstr ""
msgid "Delete column"
msgstr ""
#: src/strings.ts:2637
#: src/strings.ts:2640
msgid "Delete data"
msgstr ""
@@ -2387,7 +2386,7 @@ msgstr ""
msgid "Edit"
msgstr ""
#: src/strings.ts:2627
#: src/strings.ts:2630
msgid "Edit creation date"
msgstr ""
@@ -2417,7 +2416,7 @@ msgstr ""
msgid "Editor"
msgstr ""
#: src/strings.ts:2583
#: src/strings.ts:2586
msgid "Education plan"
msgstr ""
@@ -2605,7 +2604,7 @@ msgstr ""
msgid "Enter the recovery code to continue logging in"
msgstr ""
#: src/strings.ts:2619
#: src/strings.ts:2622
msgid "Enter title"
msgstr ""
@@ -2701,7 +2700,7 @@ msgstr ""
msgid "Experience the next level of private note taking\""
msgstr ""
#: src/strings.ts:2633
#: src/strings.ts:2636
msgid "Expiry date"
msgstr ""
@@ -2730,7 +2729,7 @@ msgstr ""
msgid "Export as{0}"
msgstr ""
#: src/strings.ts:2634
#: src/strings.ts:2637
msgid "Export CSV"
msgstr ""
@@ -2758,7 +2757,7 @@ msgstr ""
msgid "Failed"
msgstr ""
#: src/strings.ts:2638
#: src/strings.ts:2641
msgid "Failed to attach file"
msgstr ""
@@ -2895,7 +2894,7 @@ msgstr ""
msgid "Filter languages"
msgstr ""
#: src/strings.ts:2605
#: src/strings.ts:2608
msgid "Finish your purchase in the browser."
msgstr ""
@@ -3078,7 +3077,7 @@ msgstr ""
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr ""
#: src/strings.ts:2606
#: src/strings.ts:2609
msgid "Go back"
msgstr ""
@@ -3122,10 +3121,6 @@ msgstr ""
msgid "Google will remind you 2 days before your trial ends."
msgstr ""
#: src/strings.ts:2566
msgid "Google will remind you before your trial ends"
msgstr ""
#: src/strings.ts:586
msgid "Got it"
msgstr ""
@@ -3340,7 +3335,7 @@ msgstr ""
msgid "Import completed"
msgstr ""
#: src/strings.ts:2635
#: src/strings.ts:2638
msgid "Import CSV"
msgstr ""
@@ -3428,7 +3423,7 @@ msgstr ""
msgid "Invalid email"
msgstr ""
#: src/strings.ts:2643
#: src/strings.ts:2646
msgid "Invalid recovery key. Make sure to input your account recovery key, not a 2FA recovery code."
msgstr ""
@@ -3578,7 +3573,7 @@ msgstr ""
msgid "Line {line}, Column {column}"
msgstr ""
#: src/strings.ts:2617
#: src/strings.ts:2620
msgid "Line height"
msgstr ""
@@ -3751,7 +3746,7 @@ msgstr ""
msgid "Login to encrypt and sync notes"
msgstr ""
#: src/strings.ts:2610
#: src/strings.ts:2613
msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
msgstr ""
@@ -4149,7 +4144,7 @@ msgstr ""
msgid "No one can view this {type} except you."
msgstr ""
#: src/strings.ts:2613
#: src/strings.ts:2616
msgid "No password"
msgstr ""
@@ -4286,11 +4281,11 @@ msgstr ""
msgid "Notesnook"
msgstr ""
#: src/strings.ts:2598
#: src/strings.ts:2601
msgid "Notesnook Circle"
msgstr ""
#: src/strings.ts:2600
#: src/strings.ts:2603
msgid "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom."
msgstr ""
@@ -4581,7 +4576,7 @@ msgstr ""
msgid "Pinned"
msgstr ""
#: src/strings.ts:2580
#: src/strings.ts:2583
msgid "Plan limits"
msgstr ""
@@ -4845,7 +4840,7 @@ msgstr ""
msgid "Privacy mode"
msgstr ""
#: src/strings.ts:2575
#: src/strings.ts:2578
msgid "privacy policy"
msgstr ""
@@ -4917,7 +4912,7 @@ msgstr ""
msgid "Publish note"
msgstr ""
#: src/strings.ts:2614
#: src/strings.ts:2617
msgid "Publish to the web"
msgstr ""
@@ -4941,7 +4936,7 @@ msgstr ""
msgid "Published note link will be automatically deleted once it is viewed by someone."
msgstr ""
#: src/strings.ts:2568
#: src/strings.ts:2571
msgid "Purchase"
msgstr ""
@@ -5081,7 +5076,7 @@ msgstr ""
msgid "Redeem"
msgstr ""
#: src/strings.ts:2597
#: src/strings.ts:2600
msgid "Redeem code"
msgstr ""
@@ -5857,7 +5852,7 @@ msgstr ""
msgid "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
msgstr ""
#: src/strings.ts:2631
#: src/strings.ts:2634
msgid "Set expiry"
msgstr ""
@@ -6047,7 +6042,7 @@ msgstr ""
msgid "Spaces"
msgstr ""
#: src/strings.ts:2590
#: src/strings.ts:2593
msgid "Special Offer"
msgstr ""
@@ -6127,11 +6122,11 @@ msgstr ""
msgid "Submit"
msgstr ""
#: src/strings.ts:2569
#: src/strings.ts:2572
msgid "Subscribe"
msgstr ""
#: src/strings.ts:2570
#: src/strings.ts:2573
msgid "Subscribe and start free trial"
msgstr ""
@@ -6383,7 +6378,7 @@ msgstr ""
msgid "Terms of Service "
msgstr ""
#: src/strings.ts:2577
#: src/strings.ts:2580
msgid "terms of use."
msgstr ""
@@ -6415,7 +6410,7 @@ msgstr ""
msgid "Thank you for subscribing"
msgstr ""
#: src/strings.ts:2585
#: src/strings.ts:2588
msgid "Thank you for the purchase"
msgstr ""
@@ -6431,7 +6426,7 @@ msgstr ""
msgid "The {title} at {url} is not compatible with this client."
msgstr ""
#: src/strings.ts:2630
#: src/strings.ts:2633
msgid "The incoming note could not be unlocked with the provided password. Enter the correct password for the incoming note"
msgstr ""
@@ -6439,7 +6434,7 @@ msgstr ""
msgid "The information above will be publically available at"
msgstr ""
#: src/strings.ts:2604
#: src/strings.ts:2607
msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
msgstr ""
@@ -6511,7 +6506,7 @@ msgstr ""
msgid "This error usually means the search index is corrupted."
msgstr ""
#: src/strings.ts:2643
#: src/strings.ts:2647
msgid "This feature is not available on this plan."
msgstr ""
@@ -6519,7 +6514,7 @@ msgstr ""
msgid "This image cannot be previewed"
msgstr ""
#: src/strings.ts:2571
#: src/strings.ts:2574
msgid "This is a one time purchase, no subscription."
msgstr ""
@@ -6532,7 +6527,7 @@ msgstr ""
msgid "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co."
msgstr ""
#: src/strings.ts:2636
#: src/strings.ts:2639
msgid "This note is empty"
msgstr ""
@@ -6726,7 +6721,7 @@ msgstr ""
msgid "Unfavorite"
msgstr ""
#: src/strings.ts:2581
#: src/strings.ts:2584
msgid "Unlimited"
msgstr ""
@@ -6742,7 +6737,7 @@ msgstr ""
msgid "Unlock"
msgstr ""
#: src/strings.ts:2628
#: src/strings.ts:2631
msgid "Unlock incoming note"
msgstr ""
@@ -6759,7 +6754,7 @@ msgstr ""
msgid "Unlock note to delete it"
msgstr ""
#: src/strings.ts:2639
#: src/strings.ts:2642
msgid "Unlock note to merge conflicts"
msgstr ""
@@ -6811,7 +6806,7 @@ msgstr ""
msgid "Unregister"
msgstr ""
#: src/strings.ts:2632
#: src/strings.ts:2635
msgid "Unset expiry"
msgstr ""
@@ -6860,7 +6855,7 @@ msgstr ""
msgid "Upgrade to Pro"
msgstr ""
#: src/strings.ts:2596
#: src/strings.ts:2599
msgid "Upgrade to redeem"
msgstr ""
@@ -6999,7 +6994,7 @@ msgstr ""
msgid "v{version} available"
msgstr ""
#: src/strings.ts:2644
#: src/strings.ts:2649
msgid "Value must be between {min} and {max}"
msgstr ""
@@ -7091,7 +7086,7 @@ msgstr ""
msgid "View your recovery codes to recover your account in case you lose access to your two-factor authentication methods."
msgstr ""
#: src/strings.ts:2611
#: src/strings.ts:2614
msgid "Views"
msgstr ""
@@ -7159,7 +7154,7 @@ msgstr ""
msgid "Week"
msgstr ""
#: src/strings.ts:2624
#: src/strings.ts:2627
msgid "Week format"
msgstr ""
@@ -7176,7 +7171,7 @@ msgstr ""
msgid "Welcome back!"
msgstr ""
#: src/strings.ts:2584
#: src/strings.ts:2587
msgid "Welcome to Notesnook {plan}"
msgstr ""
@@ -7253,7 +7248,7 @@ msgstr ""
msgid "You also agree to recieve marketing emails from us which you can opt-out of from app settings."
msgstr ""
#: src/strings.ts:2589
#: src/strings.ts:2592
msgid "You are already subscribed to this plan."
msgstr ""
@@ -7285,7 +7280,7 @@ msgstr ""
msgid "You can change the theme at any time from Settings or the side menu."
msgstr ""
#: src/strings.ts:2592
#: src/strings.ts:2595
msgid "You can change your subscription plan from the web app"
msgstr ""
@@ -7350,7 +7345,7 @@ msgstr ""
msgid "You have been logged out."
msgstr ""
#: src/strings.ts:2588
#: src/strings.ts:2591
msgid "You have made a one time purchase. To change your plan please contact support."
msgstr ""
@@ -7471,7 +7466,7 @@ msgstr ""
msgid "Your current 2FA method is {method}"
msgstr ""
#: src/strings.ts:2595
#: src/strings.ts:2598
msgid "Your current subscription does not allow changing plans"
msgstr ""
@@ -7519,7 +7514,7 @@ msgstr ""
msgid "Your free trial is ending soon"
msgstr ""
#: src/strings.ts:2623
#: src/strings.ts:2626
msgid "Your free trial is on-going. Your subscription will start on {trialExpiryDate}"
msgstr ""

View File

@@ -2563,7 +2563,10 @@ Use this if changes from other devices are not appearing on this device. This wi
],
trialPlanConditions: [
(duration: number) => t`Free ${duration} day trial, cancel any time`,
() => t`Google will remind you before your trial ends`
(platform: "ios" | "android") =>
t`${
platform === "ios" ? "Apple" : "Google"
} will remind you before your trial ends`
],
purchase: () => t`Purchase`,
subscribe: () => t`Subscribe`,