mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
45 Commits
mobile/fix
...
v3.3.16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8fca5cd98 | ||
|
|
4d87155fdd | ||
|
|
ed38bb6299 | ||
|
|
7c9e3e79df | ||
|
|
43b90070a9 | ||
|
|
9bea4adcf9 | ||
|
|
a5790de23c | ||
|
|
eb4edeea8f | ||
|
|
e3ccd504da | ||
|
|
4627d7d5df | ||
|
|
f216c1b65d | ||
|
|
4692ba7183 | ||
|
|
543825918c | ||
|
|
9f995b861b | ||
|
|
9963a6c655 | ||
|
|
1696ec5c72 | ||
|
|
7c95faad21 | ||
|
|
ca80441c14 | ||
|
|
9c497fd2bb | ||
|
|
ef5e5e5b4c | ||
|
|
ede900fdbd | ||
|
|
1758f03f93 | ||
|
|
f18db748e0 | ||
|
|
2fba2af126 | ||
|
|
1ef3d712e1 | ||
|
|
405adff8f4 | ||
|
|
9a21ca1cf4 | ||
|
|
49252e66ce | ||
|
|
4ba2e900c5 | ||
|
|
b6714b559e | ||
|
|
a6c1757cf6 | ||
|
|
3d847ea00c | ||
|
|
63453942ee | ||
|
|
cb5a7904d3 | ||
|
|
f2c088fb18 | ||
|
|
4e2fd9dde6 | ||
|
|
92c1ca7b6f | ||
|
|
5fcad35cdc | ||
|
|
2b11fcea41 | ||
|
|
3f0c24d27b | ||
|
|
71088fca99 | ||
|
|
d6f7f0c0be | ||
|
|
4044b68701 | ||
|
|
610ced00af | ||
|
|
f9fecda1f1 |
52
.github/workflows/desktop.preview.yml
vendored
52
.github/workflows/desktop.preview.yml
vendored
@@ -101,12 +101,12 @@ 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
|
||||
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
run: |
|
||||
npm run tx @notesnook/desktop:release
|
||||
cd apps/desktop
|
||||
@@ -152,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
|
||||
|
||||
@@ -164,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
|
||||
@@ -215,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
|
||||
@@ -223,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: |
|
||||
@@ -231,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`;
|
||||
|
||||
@@ -198,6 +198,9 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
toolsets: {
|
||||
appimage: "1.0.2"
|
||||
},
|
||||
snap: {
|
||||
autoStart: false,
|
||||
confinement: "strict",
|
||||
|
||||
2132
apps/desktop/package-lock.json
generated
2132
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.13",
|
||||
"version": "3.3.16",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@types/yargs": "^17.0.33",
|
||||
"chokidar": "^4.0.3",
|
||||
"electron": "^37.0.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-builder": "^26.8.1",
|
||||
"esbuild": "0.21.5",
|
||||
"node-abi": "^4.5.0",
|
||||
"node-gyp-build": "^4.8.4",
|
||||
|
||||
@@ -27,6 +27,7 @@ import crypto from "crypto";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const root = path.join(__dirname, "..");
|
||||
const RUNNING_PROCESSES = [];
|
||||
const RESTARTABLE_PROCESSES = [];
|
||||
let lastBundleHash = null;
|
||||
@@ -36,7 +37,7 @@ const ENV = {
|
||||
FORCE_COLOR: "false",
|
||||
COLOR: "0"
|
||||
};
|
||||
process.chdir(path.join(__dirname, ".."));
|
||||
process.chdir(root);
|
||||
|
||||
await onChange(true);
|
||||
|
||||
@@ -55,13 +56,16 @@ async function onChange(first) {
|
||||
if (first) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
await exec("npm rebuild electron --verbose --foreground-scripts");
|
||||
await exec(
|
||||
"npm run postinstall --verbose",
|
||||
path.join(root, "node_modules", "electron")
|
||||
);
|
||||
|
||||
await exec("yarn electron-builder install-app-deps");
|
||||
await exec("yarn electron-builder install-app-deps", root);
|
||||
}
|
||||
|
||||
await exec(`yarn run bundle`);
|
||||
await exec(`yarn run build`);
|
||||
await exec(`yarn run bundle`, root);
|
||||
await exec(`yarn run build`, root);
|
||||
|
||||
if (await isBundleSame()) {
|
||||
console.log("Bundle is same. Doing nothing.");
|
||||
@@ -111,7 +115,7 @@ function spawnAndWaitUntil(cmd, cwd, predicate) {
|
||||
|
||||
async function exec(cmd, cwd) {
|
||||
try {
|
||||
console.log(">", cmd);
|
||||
console.log(">", cmd, cwd);
|
||||
|
||||
return execSync(cmd, {
|
||||
env: ENV,
|
||||
|
||||
@@ -178,7 +178,9 @@ export const osIntegrationRouter = t.router({
|
||||
})
|
||||
});
|
||||
notification.show();
|
||||
if (input.urgency === "critical") {
|
||||
if (input.urgency === "critical" && process.platform !== "linux") {
|
||||
// due to an Electron bug in versions below 40.x, shell.beep() causes a segfault on Linux.
|
||||
// TODO: Remove when migrating to Electron 40.x or newer.
|
||||
shell.beep();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ async function configureAutoUpdater() {
|
||||
config.releaseTrack === "stable" &&
|
||||
autoUpdater.currentVersion.prerelease.length > 0;
|
||||
autoUpdater.allowPrerelease = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
// Do NOT auto-install on quit. On Windows, if the system shuts down while
|
||||
// the NSIS installer is running, it first removes all old files and then
|
||||
// gets killed before copying new ones — leaving an empty install directory.
|
||||
// Updates should only be installed when the user explicitly triggers it.
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.disableWebInstaller = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ android {
|
||||
if (project.hasProperty("prBuildNumber")) {
|
||||
versionCode Integer.parseInt(prBuildNumber())
|
||||
} else {
|
||||
versionCode 3099
|
||||
versionCode 3100
|
||||
}
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
- Fix clearing note title adds generated title instead of staying empty
|
||||
- Improved table cell selection, table scrolling, and table cell editing on mobile
|
||||
- Fixed app on launch randomly focuses editor instead of notes list
|
||||
- Fixed file size check shows error but continues adding the file to editor
|
||||
- Note title headline format should get content from first paragraph
|
||||
- Minor bug fixes and performance improvements
|
||||
- Bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
Thank you for using Notesnook!
|
||||
@@ -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({
|
||||
@@ -249,6 +194,7 @@ export const VaultDialog: React.FC = () => {
|
||||
close();
|
||||
}, 100);
|
||||
} else {
|
||||
setLoading(false);
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
@@ -282,6 +228,10 @@ export const VaultDialog: React.FC = () => {
|
||||
setLoading(false);
|
||||
close();
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
message: strings.vaultCleared(),
|
||||
type: "success"
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
@@ -292,6 +242,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 +322,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 +382,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 +423,7 @@ export const VaultDialog: React.FC = () => {
|
||||
}, [
|
||||
biometricUnlock,
|
||||
isBiometryEnrolled,
|
||||
enrollFingerprint,
|
||||
openInEditor,
|
||||
shareNote,
|
||||
deleteNote,
|
||||
@@ -464,33 +448,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 +493,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 +621,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);
|
||||
@@ -731,7 +746,8 @@ export const VaultDialog: React.FC = () => {
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
paddingTop: 12,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
@@ -798,7 +814,8 @@ export const VaultDialog: React.FC = () => {
|
||||
!isBiometryAvailable ||
|
||||
isCreateVault ||
|
||||
isChangePassword ||
|
||||
isCustomAction ? null : (
|
||||
isCustomAction ||
|
||||
isDeleteVault ? null : (
|
||||
<Button
|
||||
onPress={() =>
|
||||
onPressFingerprintAuth(strings.unlockNote(), "")
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -18,14 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFeaturesTable } from "@notesnook/common";
|
||||
import {
|
||||
EV,
|
||||
EVENTS,
|
||||
Plan,
|
||||
SKUResponse,
|
||||
SubscriptionPlan,
|
||||
User
|
||||
} from "@notesnook/core";
|
||||
import { EVENTS, Plan, SubscriptionPlan, User } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
@@ -55,6 +48,7 @@ import {
|
||||
TECHLORE_SVG,
|
||||
XDA_SVG
|
||||
} from "../../assets/images/assets";
|
||||
import { db } from "../../common/database";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import usePricingPlans, {
|
||||
PlanOverView,
|
||||
@@ -76,7 +70,6 @@ import { IconButton } from "../ui/icon-button";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { db } from "../../common/database";
|
||||
|
||||
const Steps = {
|
||||
select: 1,
|
||||
@@ -119,7 +112,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
|
||||
}
|
||||
setStep(Steps.buy);
|
||||
}
|
||||
}, [routeParams.state]);
|
||||
}, [pricingPlans, routeParams.state]);
|
||||
|
||||
useEffect(() => {
|
||||
let listener: NativeEventSubscription;
|
||||
@@ -137,7 +130,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
|
||||
return () => {
|
||||
listener?.remove();
|
||||
};
|
||||
}, [isFocused, step]);
|
||||
}, [isFocused, routeParams.context, step]);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = db.eventManager.subscribe(
|
||||
@@ -154,7 +147,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
|
||||
return () => {
|
||||
sub?.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
}, [routeParams.context]);
|
||||
|
||||
const is5YearPlanSelected = (
|
||||
isGithubRelease
|
||||
@@ -936,7 +929,10 @@ const PricingPlanCard = ({
|
||||
setStep: (step: number) => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [regionalDiscount, setRegionaDiscount] = useState<SKUResponse>();
|
||||
const regionalDiscount =
|
||||
annualBilling && plan.id === "pro"
|
||||
? pricingPlans?.regionalDiscount
|
||||
: undefined;
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet = width > 600;
|
||||
|
||||
@@ -959,26 +955,6 @@ const PricingPlanCard = ({
|
||||
annualBilling
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (pricingPlans?.isGithubRelease || !annualBilling) return;
|
||||
pricingPlans
|
||||
?.getRegionalDiscount(
|
||||
plan.id,
|
||||
pricingPlans.isGithubRelease
|
||||
? (WebPlan?.period as string)
|
||||
: `notesnook.${plan.id}.${annualBilling ? "yearly" : "monthly"}`
|
||||
)
|
||||
.then((value) => {
|
||||
setRegionaDiscount(value);
|
||||
});
|
||||
}, [annualBilling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!annualBilling) {
|
||||
setRegionaDiscount(undefined);
|
||||
}
|
||||
}, [annualBilling]);
|
||||
|
||||
const isSubscribed =
|
||||
product?.productId &&
|
||||
pricingPlans?.user?.subscription?.productId?.includes(plan.id) &&
|
||||
@@ -1008,8 +984,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);
|
||||
}}
|
||||
|
||||
@@ -27,7 +27,11 @@ import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { db } from "../../common/database";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import {
|
||||
eSendEvent,
|
||||
sendItemUpdateEvent,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
@@ -63,6 +67,7 @@ const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
|
||||
useRelationStore.getState().update();
|
||||
setColorNotes();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
sendItemUpdateEvent(item.id, "color");
|
||||
eSendEvent(refreshNotesPage);
|
||||
};
|
||||
|
||||
@@ -127,7 +132,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
}
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
setVisible(true);
|
||||
}, []);
|
||||
}, [colorFeature]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -140,6 +145,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
useRelationStore.getState().update();
|
||||
useMenuStore.getState().setColorNotes();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
sendItemUpdateEvent(color.id, "color");
|
||||
eSendEvent(refreshNotesPage);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -16,13 +16,14 @@ 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 { Plan, SKUResponse } from "@notesnook/core";
|
||||
import { Plan } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { 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) {
|
||||
@@ -321,7 +330,10 @@ const ProductItem = (props: {
|
||||
productId: string;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [regionalDiscount, setRegionaDiscount] = useState<SKUResponse>();
|
||||
const regionalDiscount =
|
||||
props.productId === "notesnook.pro.yearly"
|
||||
? props.pricingPlans.regionalDiscount
|
||||
: undefined;
|
||||
const product =
|
||||
props.pricingPlans?.currentPlan?.subscriptions?.[
|
||||
regionalDiscount?.sku || props.productId
|
||||
@@ -358,27 +370,19 @@ const ProductItem = (props: {
|
||||
props.pricingPlans.user?.subscription?.productId ===
|
||||
(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);
|
||||
});
|
||||
}, []);
|
||||
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 +424,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 +439,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";
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -28,7 +28,11 @@ import {
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import {
|
||||
eAfterSync,
|
||||
eCloseSheet,
|
||||
eMenuItemUpdate
|
||||
} from "../../../utils/events";
|
||||
import { SideMenuItem } from "../../../utils/menu-items";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
@@ -37,12 +41,25 @@ import AppIcon from "../../ui/AppIcon";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import PaywallSheet from "../paywall";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import { db } from "../../../common/database";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const featuresAvailable = useAreFeaturesAvailable([
|
||||
"customHomepage",
|
||||
"customizableSidebar"
|
||||
]);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const trash = useTrashStore((state) => state.items);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAppLoading) {
|
||||
useTrashStore.getState().refresh();
|
||||
}
|
||||
}, [isAppLoading]);
|
||||
|
||||
return !featuresAvailable ? null : (
|
||||
<View
|
||||
style={{
|
||||
@@ -100,7 +117,38 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
},
|
||||
icon: "sort-ascending",
|
||||
locked: !featuresAvailable?.customizableSidebar.isAllowed
|
||||
}
|
||||
},
|
||||
...(item.id === "Trash"
|
||||
? [
|
||||
{
|
||||
title: strings.clearTrash(),
|
||||
onPress: async () => {
|
||||
if (!trash || trash?.length === 0) return;
|
||||
eSendEvent(eCloseSheet);
|
||||
setTimeout(() => {
|
||||
presentDialog({
|
||||
title: strings.clearTrashConfirm(),
|
||||
paragraph: strings.clearTrashDesc(),
|
||||
positiveText: strings.clear(),
|
||||
positivePress: async () => {
|
||||
await db.trash.clear();
|
||||
useTrashStore.getState().clear();
|
||||
eSendEvent(eMenuItemUpdate);
|
||||
eSendEvent(eAfterSync);
|
||||
ToastManager.show({
|
||||
message: strings.trashCleared(),
|
||||
type: "success"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
icon: "delete-sweep-outline",
|
||||
disabled: !trash || trash?.length === 0
|
||||
}
|
||||
]
|
||||
: [])
|
||||
].map((item) => (
|
||||
<Pressable
|
||||
key={item.title}
|
||||
@@ -112,7 +160,7 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
gap: DefaultAppStyles.GAP_SMALL,
|
||||
borderRadius: 0,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
opacity: item.locked ? 0.6 : 1
|
||||
opacity: item.disabled || item.locked ? 0.6 : 1
|
||||
}}
|
||||
onPress={() => {
|
||||
item.onPress();
|
||||
|
||||
@@ -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,12 +28,15 @@ 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";
|
||||
import { requestInAppReview } from "../../../services/app-review";
|
||||
import { presentSheet, ToastManager } from "../../../services/event-manager";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useAttachmentStore } from "../../../stores/use-attachment-store";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
@@ -46,7 +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);
|
||||
@@ -55,7 +59,7 @@ async function fetchMonographData(noteId: string) {
|
||||
: undefined;
|
||||
return {
|
||||
monograph,
|
||||
monographId,
|
||||
monographId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ const PublishNoteSheet = ({
|
||||
|
||||
await monographData.execute();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(eMenuItemUpdate);
|
||||
setPublishLoading(false);
|
||||
}
|
||||
requestInAppReview();
|
||||
@@ -138,6 +143,7 @@ const PublishNoteSheet = ({
|
||||
await db.monographs.unpublish(note.id);
|
||||
monographData.execute();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(eMenuItemUpdate);
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -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,18 +24,22 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useTotalNotes } from "../../hooks/use-db-item";
|
||||
|
||||
import { db } from "../../common/database";
|
||||
import { eSubscribeEvent } from "../../services/event-manager";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
subscribeToItemUpdate
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import useNavigationStore, {
|
||||
RouteParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { eAfterSync } from "../../utils/events";
|
||||
import { eAfterSync, eMenuItemUpdate } from "../../utils/events";
|
||||
import { SideMenuItem } from "../../utils/menu-items";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useSideBarDraggingStore } from "./dragging-store";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
|
||||
export function MenuItem({
|
||||
item,
|
||||
@@ -56,13 +60,19 @@ export function MenuItem({
|
||||
const totalNotes = useTotalNotes(
|
||||
item.dataType as "notebook" | "tag" | "color"
|
||||
);
|
||||
const update = useRelationStore((state) => state.updater);
|
||||
const getTotalNotesRef = useRef(totalNotes.getTotalNotes);
|
||||
getTotalNotesRef.current = totalNotes.getTotalNotes;
|
||||
|
||||
const menuItemCount = !item.data
|
||||
? itemCount
|
||||
: totalNotes.totalNotes(item.data.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (item.data) {
|
||||
getTotalNotesRef.current([item.data?.id]);
|
||||
}
|
||||
}, [item.data, update]);
|
||||
|
||||
useEffect(() => {
|
||||
const onSyncComplete = async () => {
|
||||
try {
|
||||
@@ -84,7 +94,9 @@ export function MenuItem({
|
||||
setItemCount(await db.notes.archived.count());
|
||||
break;
|
||||
case "Trash":
|
||||
setItemCount((await db.trash.all()).length);
|
||||
{
|
||||
setItemCount((await db.trash.all()).length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -94,10 +106,20 @@ export function MenuItem({
|
||||
/** Empty */
|
||||
}
|
||||
};
|
||||
const event = eSubscribeEvent(eAfterSync, onSyncComplete);
|
||||
const events = [eSubscribeEvent(eAfterSync, onSyncComplete)];
|
||||
|
||||
if (!item.data) {
|
||||
events.push(eSubscribeEvent(eMenuItemUpdate, onSyncComplete));
|
||||
}
|
||||
|
||||
if (item.data?.id) {
|
||||
events.push(
|
||||
subscribeToItemUpdate(item?.data?.id, item?.data?.type, onSyncComplete)
|
||||
);
|
||||
}
|
||||
onSyncComplete();
|
||||
return () => {
|
||||
event?.unsubscribe();
|
||||
events?.forEach((e) => e?.unsubscribe());
|
||||
};
|
||||
}, [item.data, item.id]);
|
||||
|
||||
@@ -151,8 +173,8 @@ export function MenuItem({
|
||||
item.icon === "crown"
|
||||
? colors.static.yellow
|
||||
: isFocused
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
size={AppFontSize.md}
|
||||
/>
|
||||
|
||||
@@ -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";
|
||||
@@ -35,6 +36,7 @@ import AppIcon from "../ui/AppIcon";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
|
||||
export const NotebookItem = ({
|
||||
index,
|
||||
@@ -70,13 +72,14 @@ export const NotebookItem = ({
|
||||
const notebook = item.notebook;
|
||||
const isFocused = focused;
|
||||
const { totalNotes, getTotalNotes } = useTotalNotes("notebook");
|
||||
const updater = useRelationStore(state => state.updater);
|
||||
const getTotalNotesRef = React.useRef(getTotalNotes);
|
||||
getTotalNotesRef.current = getTotalNotes;
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
useEffect(() => {
|
||||
getTotalNotesRef.current([item.notebook.id]);
|
||||
}, [item.notebook]);
|
||||
}, [item.notebook, updater]);
|
||||
|
||||
useEffect(() => {
|
||||
const onNotebookUpdate = (id?: string) => {
|
||||
@@ -101,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 }) => {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
useSideMenuNotebookTreeStore
|
||||
} from "./stores";
|
||||
import { LegendList } from "@legendapp/list";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
useSideMenuNotebookSelectionStore.setState({
|
||||
multiSelect: true
|
||||
});
|
||||
@@ -52,6 +53,7 @@ export const SideMenuNotebooks = () => {
|
||||
const [filteredNotebooks, setFilteredNotebooks] = React.useState(notebooks);
|
||||
const searchTimer = React.useRef<NodeJS.Timeout>(undefined);
|
||||
const lastQuery = React.useRef<string>(undefined);
|
||||
const updater = useRelationStore(state => state.updater);
|
||||
const loadRootNotebooks = React.useCallback(async () => {
|
||||
if (!filteredNotebooks) return;
|
||||
const _notebooks: Notebook[] = [];
|
||||
@@ -66,9 +68,6 @@ export const SideMenuNotebooks = () => {
|
||||
|
||||
const updateNotebooks = React.useCallback(() => {
|
||||
if (lastQuery.current) {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: true
|
||||
// });
|
||||
db.lookup
|
||||
.notebooks(lastQuery.current)
|
||||
.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
@@ -76,16 +75,13 @@ export const SideMenuNotebooks = () => {
|
||||
setFilteredNotebooks(filtered);
|
||||
});
|
||||
} else {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: false
|
||||
// });
|
||||
setFilteredNotebooks(notebooks);
|
||||
}
|
||||
}, [notebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNotebooks();
|
||||
}, [updateNotebooks]);
|
||||
}, [updateNotebooks,updater]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
||||
@@ -38,6 +38,7 @@ import { SideMenuHeader } from "./side-menu-header";
|
||||
import { SideMenuListEmpty } from "./side-menu-list-empty";
|
||||
import { useSideMenuTagsSelectionStore } from "./stores";
|
||||
import { LegendList, LegendListRenderItemProps } from "@legendapp/list";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
|
||||
const TagItem = (props: {
|
||||
tags: VirtualizedGrouping<Tag>;
|
||||
@@ -55,12 +56,13 @@ const TagItem = (props: {
|
||||
const totalNotes = useTotalNotes("tag");
|
||||
const totalNotesRef = React.useRef(totalNotes);
|
||||
totalNotesRef.current = totalNotes;
|
||||
const updater = useRelationStore(state => state.updater);
|
||||
|
||||
useEffect(() => {
|
||||
if (item?.id) {
|
||||
totalNotesRef.current?.getTotalNotes([item?.id]);
|
||||
}
|
||||
}, [item]);
|
||||
}, [item, updater]);
|
||||
|
||||
return (
|
||||
<View
|
||||
|
||||
@@ -38,10 +38,10 @@ import {
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { eHideToast, eShowToast } from "../../utils/events";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const Toast = ({ context = "global" }) => {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
@@ -119,11 +119,7 @@ export const Toast = ({ context = "global" }) => {
|
||||
width: DDS.isTab ? dimensions.width / 2 : "100%",
|
||||
alignItems: "center",
|
||||
alignSelf: "center",
|
||||
bottom:
|
||||
Platform.OS === "android"
|
||||
? Math.max(insets.bottom, 40)
|
||||
: Math.max(insets.bottom, 40) +
|
||||
(keyboard.keyboardShown ? keyboard.keyboardHeight : 0),
|
||||
bottom: insets.bottom + 15,
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
elevation: 15
|
||||
@@ -158,22 +154,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)
|
||||
@@ -382,6 +382,16 @@ export const useActions = ({
|
||||
}
|
||||
|
||||
const deleteItem = async () => {
|
||||
if (isPublished) {
|
||||
ToastManager.show({
|
||||
heading: strings.notePublished(),
|
||||
message: strings.unpublishToDelete(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
close();
|
||||
await sleep(300);
|
||||
|
||||
@@ -1237,7 +1247,8 @@ export const useActions = ({
|
||||
: strings.moveToTrash(),
|
||||
icon: "delete-outline",
|
||||
type: "error",
|
||||
onPress: deleteItem
|
||||
onPress: deleteItem,
|
||||
locked: isPublished
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1275,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([
|
||||
@@ -24,11 +37,10 @@ export default function useFeatureManager() {
|
||||
"disableTrashCleanup",
|
||||
"fullOfflineMode"
|
||||
]);
|
||||
const user = useUserStore((state) => state.user);
|
||||
const plan = useUserStore((state) => state.user?.subscription?.plan);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !features) return;
|
||||
if (!useUserStore.getState().user || !features) return;
|
||||
|
||||
if (!features.createNoteFromNotificationDrawer.isAllowed) {
|
||||
SettingsService.setProperty("notifNotes", false);
|
||||
@@ -69,6 +81,7 @@ export default function useFeatureManager() {
|
||||
db.settings.setDefaultTag(undefined);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [features, plan]);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -16,16 +16,17 @@ 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 { Plan, SubscriptionPlan, SubscriptionPlanId } from "@notesnook/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Plan, SubscriptionPlan } from "@notesnook/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import { Platform } from "react-native";
|
||||
import Config from "react-native-config";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import PremiumService from "../services/premium";
|
||||
import SettingsService from "../services/settings";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import SettingsService from "../services/settings";
|
||||
function numberWithCommas(x: string) {
|
||||
const parts = x.toString().split(".");
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
@@ -155,9 +156,18 @@ const planIdToIndex = (planId: string) => {
|
||||
return planIndex;
|
||||
};
|
||||
let WebPlanCache: Plan[];
|
||||
const isGithubRelease = Config.GITHUB_RELEASE === "true";
|
||||
const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
const isGithubRelease = Config.GITHUB_RELEASE === "true";
|
||||
const user = useUserStore((state) => state.user);
|
||||
const regionalDiscount = useAsync(
|
||||
() =>
|
||||
db.pricing.sku(
|
||||
Platform.OS === "android" ? "google" : "apple",
|
||||
"yearly",
|
||||
"pro"
|
||||
),
|
||||
[]
|
||||
);
|
||||
const [currentPlan, setCurrentPlan] = useState<string>(
|
||||
options?.planId || pricingPlans[2].id
|
||||
);
|
||||
@@ -172,17 +182,20 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
const [userCanRequestTrial, setUserCanRequestTrial] = useState(false);
|
||||
const [webPricingPlans, setWebPricingPlans] = useState<Plan[]>([]);
|
||||
|
||||
const getProduct = (planId: string, skuId: string) => {
|
||||
if (isGithubRelease)
|
||||
return webPricingPlans.find(
|
||||
(plan) => planIdToIndex(planId) === plan.plan && skuId === plan.period
|
||||
);
|
||||
const getProduct = React.useCallback(
|
||||
(planId: string, skuId: string) => {
|
||||
if (isGithubRelease)
|
||||
return webPricingPlans.find(
|
||||
(plan) => planIdToIndex(planId) === plan.plan && skuId === plan.period
|
||||
);
|
||||
|
||||
return (
|
||||
plans.find((p) => p.id === planId)?.subscriptions?.[skuId] ||
|
||||
plans.find((p) => p.id === planId)?.products?.[skuId]
|
||||
);
|
||||
};
|
||||
return (
|
||||
plans.find((p) => p.id === planId)?.subscriptions?.[skuId] ||
|
||||
plans.find((p) => p.id === planId)?.products?.[skuId]
|
||||
);
|
||||
},
|
||||
[plans, webPricingPlans]
|
||||
);
|
||||
|
||||
const getProductAndroid = (planId: string, skuId: string) => {
|
||||
if (isGithubRelease)
|
||||
@@ -196,37 +209,45 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
return getProduct(planId, skuId) as RNIap.SubscriptionIOS;
|
||||
};
|
||||
|
||||
const hasTrialOffer = (planId?: string, productId?: string) => {
|
||||
if (!selectedProductSku && !productId) return false;
|
||||
const hasTrialOffer = React.useCallback(
|
||||
(planId?: string, productId?: string) => {
|
||||
if (!selectedProductSku && !productId) return false;
|
||||
|
||||
if (productId?.includes("5year")) return false;
|
||||
if (isGithubRelease) {
|
||||
if (
|
||||
user?.subscription?.trialsAvailed?.some(
|
||||
(plan) => plan === planIdToIndex(planId || currentPlan)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
if (productId?.includes("5year")) return false;
|
||||
if (isGithubRelease) {
|
||||
if (
|
||||
user?.subscription?.trialsAvailed?.some(
|
||||
(plan) => plan === planIdToIndex(planId || currentPlan)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Platform.OS === "ios"
|
||||
? (
|
||||
getProduct(
|
||||
planId || currentPlan,
|
||||
productId || selectedProductSku
|
||||
) as RNIap.SubscriptionIOS
|
||||
)?.introductoryPricePaymentModeIOS === "FREETRIAL"
|
||||
: (
|
||||
getProduct(
|
||||
planId || currentPlan,
|
||||
productId || selectedProductSku
|
||||
) as RNIap.SubscriptionAndroid
|
||||
)?.subscriptionOfferDetails?.[0]?.pricingPhases?.pricingPhaseList
|
||||
?.length > 1;
|
||||
};
|
||||
return Platform.OS === "ios"
|
||||
? (
|
||||
getProduct(
|
||||
planId || currentPlan,
|
||||
productId || selectedProductSku
|
||||
) as RNIap.SubscriptionIOS
|
||||
)?.introductoryPricePaymentModeIOS === "FREETRIAL"
|
||||
: (
|
||||
getProduct(
|
||||
planId || currentPlan,
|
||||
productId || selectedProductSku
|
||||
) as RNIap.SubscriptionAndroid
|
||||
)?.subscriptionOfferDetails?.[0]?.pricingPhases?.pricingPhaseList
|
||||
?.length > 1;
|
||||
},
|
||||
[
|
||||
currentPlan,
|
||||
getProduct,
|
||||
selectedProductSku,
|
||||
user?.subscription?.trialsAvailed
|
||||
]
|
||||
);
|
||||
|
||||
// user && (!user.subscription || !user.subscription.expiry) ? true : false;
|
||||
|
||||
@@ -255,12 +276,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 +300,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 +456,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 +499,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 +554,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 +612,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 +632,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
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -645,21 +670,6 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
);
|
||||
};
|
||||
|
||||
async function getRegionalDiscount(plan: string, productId: string) {
|
||||
if (productId !== "notesnook.pro.yearly") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return await db.pricing.sku(
|
||||
Platform.OS === "android" ? "google" : "apple",
|
||||
"yearly",
|
||||
plan as SubscriptionPlanId
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
function isSubscribedToPlan(planId: string) {
|
||||
if (!PremiumService.get()) return false;
|
||||
return user?.subscription?.productId?.includes(planId);
|
||||
@@ -730,7 +740,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
(plan) => plan.plan === planIndex && plan.period === period
|
||||
);
|
||||
},
|
||||
getRegionalDiscount,
|
||||
regionalDiscount: regionalDiscount.result,
|
||||
isGithubRelease: isGithubRelease,
|
||||
isSubscribed: () => user?.subscription?.plan !== SubscriptionPlan.FREE,
|
||||
finish: () => options?.onBuy?.()
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -45,7 +45,10 @@ import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
import { Notice } from "../../components/ui/notice";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import {
|
||||
sendItemUpdateEvent,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
const {
|
||||
@@ -175,6 +178,7 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
|
||||
updateNotebook(item.id);
|
||||
}
|
||||
}
|
||||
sendItemUpdateEvent(id, "notebook");
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate();
|
||||
@@ -237,9 +241,8 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: async () => {
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
const notebooksFeature =
|
||||
await isFeatureAvailable("notebooks");
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
@@ -388,8 +391,8 @@ const NotebookItemWrapper = React.memo(
|
||||
!selected
|
||||
? "selected"
|
||||
: !state.initialState[item.notebook.id]
|
||||
? undefined
|
||||
: "deselected"
|
||||
? undefined
|
||||
: "deselected"
|
||||
);
|
||||
}}
|
||||
canDisableSelectionMode={false}
|
||||
|
||||
@@ -33,7 +33,10 @@ import { Pressable } from "../../components/ui/pressable";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import {
|
||||
sendItemUpdateEvent,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import {
|
||||
ItemSelection,
|
||||
@@ -80,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);
|
||||
@@ -121,7 +127,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
tags.splice(0, 0, ...noteTags);
|
||||
setTags(tags);
|
||||
},
|
||||
[]
|
||||
[ids]
|
||||
);
|
||||
|
||||
const refreshTags = useCallback(() => {
|
||||
@@ -135,7 +141,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
.sorted(db.settings.getGroupOptions("tags"))
|
||||
.then(sortAndSetTags);
|
||||
}
|
||||
}, [query]);
|
||||
}, [query, sortAndSetTags]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshTags();
|
||||
@@ -199,6 +205,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
}
|
||||
|
||||
useRelationStore.getState().update();
|
||||
sendItemUpdateEvent(id, "tag");
|
||||
useTagStore.getState().refresh();
|
||||
setQuery(undefined);
|
||||
} catch (e) {
|
||||
@@ -242,6 +249,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
sendItemUpdateEvent(id, "tag");
|
||||
useTagStore.getState().refresh();
|
||||
useRelationStore.getState().update();
|
||||
refreshTags();
|
||||
@@ -417,15 +425,15 @@ const TagItem = ({
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
name={
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -39,6 +39,7 @@ import { createItemSelectionStore } from "../../stores/item-selection-store";
|
||||
import { updateNotebook } from "../../utils/notebooks";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { sendItemUpdateEvent } from "../../services/event-manager";
|
||||
const useItemSelectionStore = createItemSelectionStore(true);
|
||||
|
||||
export const MoveNotes = (props: NavigationProps<"MoveNotes">) => {
|
||||
@@ -198,6 +199,7 @@ export const MoveNotes = (props: NavigationProps<"MoveNotes">) => {
|
||||
);
|
||||
|
||||
updateNotebook(currentNotebook.id);
|
||||
sendItemUpdateEvent(currentNotebook.id, "notebook");
|
||||
Navigation.queueRoutesForUpdate();
|
||||
Navigation.goBack();
|
||||
}}
|
||||
@@ -281,8 +283,8 @@ const SelectableNoteItem = React.memo(
|
||||
selected
|
||||
? colors.selected.icon
|
||||
: exists && !selected
|
||||
? colors.static.red
|
||||
: colors.primary.icon
|
||||
? colors.static.red
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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,23 +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 PremiumService from "../../../services/premium";
|
||||
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);
|
||||
@@ -41,7 +40,7 @@ export const ConfigureToolbar = () => {
|
||||
|
||||
const renderGroups = () => {
|
||||
return data?.map((item, index) => (
|
||||
<Group key={`group-${index}`} item={item} index={index} />
|
||||
<Group key={`group-${index}-${item.length}`} item={item} index={index} />
|
||||
));
|
||||
};
|
||||
|
||||
@@ -131,6 +130,7 @@ export const ConfigureToolbar = () => {
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
key={preset}
|
||||
scrollEventThrottle={13}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
@@ -31,7 +31,7 @@ import { renderTool } from "./common";
|
||||
import { DraggableItem, useDragState } from "./state";
|
||||
import ToolSheet from "./tool-sheet";
|
||||
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import type { ToolId } from "@notesnook/editor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -64,78 +64,87 @@ export const Group = ({
|
||||
const { colors } = useThemeColors();
|
||||
const featureAvailable = useIsFeatureAvailable("customToolbarPreset");
|
||||
|
||||
const onDrop = (data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
|
||||
if (dragged.type === "group") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
_data.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
_data.splice(fromIndex, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
// Always insert sub group at the end of the group.
|
||||
if (dragged.type === "subgroup") {
|
||||
const fromIndex = dragged.index;
|
||||
|
||||
const insertAt = _data[reciever.index] as string[];
|
||||
const insertFrom = _data[dragged.groupIndex] as string[];
|
||||
|
||||
if (typeof insertAt[insertAt.length - 1] !== "string") {
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
const onDrop = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error || strings.featureNotAvailable()
|
||||
});
|
||||
return;
|
||||
}
|
||||
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const insertFrom =
|
||||
typeof dragged.parentIndex === "number"
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
_data[groupIndex].push(insertFrom.splice(dragged.index, 1)[0] as ToolId);
|
||||
}
|
||||
if (dragged.type === "group") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
};
|
||||
_data.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
_data.splice(fromIndex, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.dragged.payload.type !== "group")
|
||||
return setRecievePosition("below");
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
};
|
||||
// Always insert sub group at the end of the group.
|
||||
if (dragged.type === "subgroup") {
|
||||
const fromIndex = dragged.index;
|
||||
|
||||
const insertAt = _data[reciever.index] as string[];
|
||||
const insertFrom = _data[dragged.groupIndex] as string[];
|
||||
|
||||
if (typeof insertAt[insertAt.length - 1] !== "string") {
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
}
|
||||
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
|
||||
}
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const insertFrom =
|
||||
typeof dragged.parentIndex === "number"
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
_data[groupIndex].push(
|
||||
insertFrom.splice(dragged.index, 1)[0] as ToolId
|
||||
);
|
||||
}
|
||||
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
},
|
||||
[featureAvailable]
|
||||
);
|
||||
|
||||
const onRecieveData = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.dragged.payload.type !== "group")
|
||||
return setRecievePosition("below");
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -157,11 +166,12 @@ export const Group = ({
|
||||
},
|
||||
{
|
||||
name: "plus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ export default function ToolSheet({
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
}) {
|
||||
const { colors } = useThemeColors();
|
||||
const data = useDragState((state) => state.data);
|
||||
const ungrouped = getUngroupedTools(data) as ToolId[];
|
||||
const [data] = useDragState((state) => [state.data]);
|
||||
const ungrouped = getUngroupedTools( data) as ToolId[];
|
||||
|
||||
const renderTool = React.useCallback(
|
||||
(item: ToolId) => {
|
||||
@@ -114,7 +114,7 @@ export default function ToolSheet({
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
maxHeight: "100%",
|
||||
maxHeight: 400,
|
||||
padding: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -33,7 +33,7 @@ import { DraggableItem, useDragState } from "./state";
|
||||
import ToolSheet from "./tool-sheet";
|
||||
import { findToolById, getToolIcon } from "./toolbar-definition";
|
||||
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import type { ToolId } from "@notesnook/editor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -78,11 +78,12 @@ export const Tool = ({
|
||||
? [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -102,11 +103,12 @@ export const Tool = ({
|
||||
},
|
||||
{
|
||||
name: "plus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -122,11 +124,12 @@ export const Tool = ({
|
||||
: [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +154,7 @@ export const Tool = ({
|
||||
if (parentIndex === undefined && !isSubgroup) {
|
||||
btns.unshift({
|
||||
name: "unfold-less-horizontal",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
if (groupIndex === undefined) return;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
const hasSubGroup = Array.isArray(
|
||||
@@ -267,6 +270,7 @@ export const Tool = ({
|
||||
style={{
|
||||
paddingLeft: 30
|
||||
}}
|
||||
key={`subgroup-${item.length}-${groupIndex}-${index}-${parentIndex}`}
|
||||
>
|
||||
{renderGroup({ index, item, parentIndex: groupIndex })}
|
||||
</View>
|
||||
@@ -290,63 +294,69 @@ export const Tool = ({
|
||||
]
|
||||
);
|
||||
|
||||
const onDrop = (data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data?.slice();
|
||||
if (!_data) return;
|
||||
const isFromSubgroup = typeof dragged?.parentIndex === "number";
|
||||
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
const insertAt = isDroppedAtSubgroup
|
||||
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
|
||||
: (_data[reciever.groupIndex] as string[]);
|
||||
const insertFrom = isFromSubgroup
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
insertAt.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
insertFrom.splice(fromIndex, 1)[0]
|
||||
);
|
||||
|
||||
// Remove the group or subgroup if it is empty.
|
||||
if (insertFrom.length === 0) {
|
||||
isFromSubgroup
|
||||
? _data[dragged.parentIndex].splice(
|
||||
_data[dragged.parentIndex].length - 1,
|
||||
1
|
||||
)
|
||||
: _data.splice(dragged.groupIndex, 1);
|
||||
const onDrop = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error || strings.featureNotAvailable()
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
};
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data?.slice();
|
||||
if (!_data) return;
|
||||
const isFromSubgroup = typeof dragged?.parentIndex === "number";
|
||||
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
|
||||
|
||||
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
};
|
||||
if (dragged.type === "tool") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
const insertAt = isDroppedAtSubgroup
|
||||
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
|
||||
: (_data[reciever.groupIndex] as string[]);
|
||||
const insertFrom = isFromSubgroup
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
insertAt.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
insertFrom.splice(fromIndex, 1)[0]
|
||||
);
|
||||
|
||||
// Remove the group or subgroup if it is empty.
|
||||
if (insertFrom.length === 0) {
|
||||
isFromSubgroup
|
||||
? _data[dragged.parentIndex].splice(
|
||||
_data[dragged.parentIndex].length - 1,
|
||||
1
|
||||
)
|
||||
: _data.splice(dragged.groupIndex, 1);
|
||||
}
|
||||
}
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
},
|
||||
[featureAvailable]
|
||||
);
|
||||
|
||||
const onRecieveData = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Animated.View layout={Layout}>
|
||||
|
||||
@@ -25,18 +25,18 @@ import {
|
||||
} from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||
import { ToolId } from "@notesnook/editor";
|
||||
|
||||
export const tools = getAllTools() as any;
|
||||
export const tools = () => getAllTools();
|
||||
export const presets: { [name: string]: ToolbarGroupDefinition[] } = {
|
||||
default: getDefaultPresets().default as any,
|
||||
minimal: getDefaultPresets().minimal as any,
|
||||
custom: []
|
||||
};
|
||||
|
||||
export function findToolById(id: keyof typeof tools): {
|
||||
export function findToolById(id: keyof ReturnType<typeof tools> ): {
|
||||
title: string;
|
||||
icon: string;
|
||||
} {
|
||||
return tools[id];
|
||||
return tools()[id];
|
||||
}
|
||||
|
||||
export function getToolIcon(id: ToolId, color: string) {
|
||||
@@ -51,13 +51,14 @@ export function getToolIcon(id: ToolId, color: string) {
|
||||
|
||||
export function getUngroupedTools(
|
||||
toolDefinition: (string | string[])[][]
|
||||
): string[] {
|
||||
const keys = Object.keys(tools);
|
||||
): string[] {
|
||||
const allTools = tools();
|
||||
const keys = Object.keys(allTools);
|
||||
|
||||
const ungrouped = [];
|
||||
const toolString = JSON.stringify(toolDefinition);
|
||||
for (const key of keys) {
|
||||
if (tools[key as ToolId].conditional) continue;
|
||||
if ((allTools[key as ToolId] as any).conditional) continue;
|
||||
if (!toolString.includes(key)) ungrouped.push(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -36,7 +36,7 @@ import { strings } from "@notesnook/intl";
|
||||
const onPressFloatingButton = () => {
|
||||
presentDialog({
|
||||
title: strings.clearTrash(),
|
||||
paragraph: strings.clearTrashConfirm(),
|
||||
paragraph: strings.clearTrashDesc(),
|
||||
positiveText: strings.clear(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: async () => {
|
||||
@@ -58,8 +58,8 @@ const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
|
||||
trashCleanupInterval === -1
|
||||
? strings.noTrashCleanupInterval()
|
||||
: trashCleanupInterval === 1
|
||||
? strings.trashCleanupIntervalTextDaily()
|
||||
: strings.trashCleanupIntervalTextDays(trashCleanupInterval),
|
||||
? strings.trashCleanupIntervalTextDaily()
|
||||
: strings.trashCleanupIntervalTextDays(trashCleanupInterval),
|
||||
loading: strings.loadingTrash()
|
||||
});
|
||||
|
||||
@@ -107,6 +107,7 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
|
||||
{trash && trash?.placeholders?.length !== 0 ? (
|
||||
<FloatingButton
|
||||
onPress={onPressFloatingButton}
|
||||
testID="trash-clear"
|
||||
alwaysVisible={true}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -17,7 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { EventHandler, EventManager, Note, NoteContent } from "@notesnook/core";
|
||||
import {
|
||||
EventHandler,
|
||||
EventManager,
|
||||
ItemType,
|
||||
Note,
|
||||
NoteContent
|
||||
} from "@notesnook/core";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { RefObject } from "react";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
@@ -84,6 +90,18 @@ export const eSubscribeEvent = <T = unknown>(
|
||||
return eventManager.subscribe(eventName, action);
|
||||
};
|
||||
|
||||
export function sendItemUpdateEvent(id: string, type: ItemType) {
|
||||
eSendEvent(`${type}:${id}`);
|
||||
}
|
||||
|
||||
export function subscribeToItemUpdate(
|
||||
id: string,
|
||||
type: ItemType,
|
||||
callback: EventHandler
|
||||
) {
|
||||
return eSubscribeEvent(`${type}:${id}`, callback);
|
||||
}
|
||||
|
||||
export const eUnSubscribeEvent = <T = unknown>(
|
||||
eventName: string,
|
||||
action: EventHandler
|
||||
|
||||
@@ -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) : [],
|
||||
|
||||
@@ -22,7 +22,8 @@ import create, { StoreApi, UseBoundStore } from "zustand";
|
||||
import { resolveItems } from "@notesnook/common";
|
||||
import { useSettingStore } from "./use-setting-store";
|
||||
import { DatabaseLogger } from "../common/database";
|
||||
import { ToastManager } from "../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../services/event-manager";
|
||||
import { eMenuItemUpdate } from "../utils/events";
|
||||
|
||||
export interface DBCollectionStore<Type extends Item> {
|
||||
items: VirtualizedGrouping<Type> | undefined;
|
||||
@@ -67,6 +68,7 @@ export default function createDBCollectionStore<Type extends Item>({
|
||||
items,
|
||||
loading: false
|
||||
});
|
||||
eSendEvent(eMenuItemUpdate);
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error, "useDBCollectionStore.refresh", {
|
||||
useDBCollectionStore: "refresh"
|
||||
|
||||
@@ -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":
|
||||
{
|
||||
|
||||
@@ -176,3 +176,4 @@ export const eOnEnterEditor = "621";
|
||||
export const eOnExitEditor = "622";
|
||||
export const eEditorReset = "623";
|
||||
export const eAfterSync = "624";
|
||||
export const eMenuItemUpdate = "625";
|
||||
@@ -34,6 +34,7 @@ import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { eUpdateNoteInEditor } from "./events";
|
||||
import { unlockVault } from "./unlock-vault";
|
||||
import { useTrashStore } from "../stores/use-trash-store";
|
||||
|
||||
export const valueLimiter = (value: number, min: number, max: number) => {
|
||||
return value < min ? min : value > max ? max : value;
|
||||
@@ -210,7 +211,9 @@ export const deleteItems = async (
|
||||
if (type === "notebook") {
|
||||
useNotebookStore.getState().refresh();
|
||||
}
|
||||
useTrashStore.getState().refresh();
|
||||
useMenuStore.getState().setMenuPins();
|
||||
useRelationStore.getState().update();
|
||||
};
|
||||
|
||||
export const openLinkInBrowser = async (link: string) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user