mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
8 Commits
mobile/fix
...
v3.3.15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c497fd2bb | ||
|
|
ede900fdbd | ||
|
|
1758f03f93 | ||
|
|
f18db748e0 | ||
|
|
2fba2af126 | ||
|
|
1ef3d712e1 | ||
|
|
405adff8f4 | ||
|
|
9a21ca1cf4 |
51
.github/workflows/desktop.preview.yml
vendored
51
.github/workflows/desktop.preview.yml
vendored
@@ -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`;
|
||||
|
||||
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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[]);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 : (
|
||||
|
||||
@@ -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)) ||
|
||||
[];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
isEncryptedContent,
|
||||
Note,
|
||||
NoteContent,
|
||||
SessionContentItem,
|
||||
TrashOrItem
|
||||
} from "@notesnook/core";
|
||||
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -132,7 +132,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
}
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
setVisible(true);
|
||||
}, []);
|
||||
}, [colorFeature]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -77,7 +77,7 @@ export const TagStrip = ({ item, close }) => {
|
||||
.then((tags) => {
|
||||
setTags(tags);
|
||||
});
|
||||
}, []);
|
||||
}, [item]);
|
||||
|
||||
return tags?.length > 0 ? (
|
||||
<View
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ export const Editor = ({
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
}, [editor.ref, editorRef]);
|
||||
|
||||
return (
|
||||
<View
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) : [],
|
||||
|
||||
@@ -28,6 +28,7 @@ export type TreeItem = {
|
||||
notebook: Notebook;
|
||||
depth: number;
|
||||
hasChildren: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function removeTreeItem(tree: TreeItem[], id: string) {
|
||||
|
||||
@@ -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":
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
2394
apps/mobile/package-lock.json
generated
2394
apps/mobile/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user