Compare commits

..

2 Commits

Author SHA1 Message Date
Ammar Ahmed
64b806ff5a mobile: fix android toolbar 2025-06-25 10:21:44 +05:00
Ammar Ahmed
997d24de5e mobile: fix android toolbar hidden behind keyboard on android 15 2025-06-20 14:48:45 +05:00
39 changed files with 1680 additions and 406 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.2.2",
"version": "3.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.2.2",
"version": "3.2.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -15,7 +15,7 @@
"@notesnook/ui": "file:../../packages/ui",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.10.0",
"better-sqlite3-multiple-ciphers": "11.3.0",
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
@@ -2282,9 +2282,9 @@
"license": "MIT"
},
"node_modules/better-sqlite3-multiple-ciphers": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-11.10.0.tgz",
"integrity": "sha512-/dKO3lKuJFbmuzh80uN2cmMsz8iyTskGB2l/fd9X6rt1P3EPIOvRUIxD7Qim8gLygUPB/u+db8byZGumOOdp3g==",
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-11.3.0.tgz",
"integrity": "sha512-F0+gYaT8drCyHpujgMjS4RRAElVdAtif0uH/v4rA5cLTR5v25zDXTkj2/ie8A26Www+vT60pCF0MD+MGzIcUzw==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.2.2",
"version": "3.2.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -30,7 +30,7 @@
"@notesnook/ui": "file:../../packages/ui",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.10.0",
"better-sqlite3-multiple-ciphers": "11.3.0",
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",

View File

@@ -1,5 +1,5 @@
diff --git a/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi b/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
index 1a14ecd..ff938f1 100644
index 1a14ecd..dabcce2 100644
--- a/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
+++ b/node_modules/better-sqlite3-multiple-ciphers/deps/defines.gypi
@@ -38,5 +38,6 @@

View File

@@ -33,8 +33,6 @@ export class SQLite {
initialized = false;
preparedStatements: Map<string, Statement<unknown[]>> = new Map();
retryCounter: Record<string, number> = {};
extensionsLoaded = false;
constructor() {
console.log("new sqlite worker");
}
@@ -48,6 +46,10 @@ export class SQLite {
this.sqlite = require("better-sqlite3-multiple-ciphers")(
filePath
).unsafeMode(true);
const betterTrigram = require("sqlite-better-trigram");
const fts5Html = require("sqlite3-fts5-html");
betterTrigram.load(this.sqlite);
fts5Html.load(this.sqlite);
}
/**
@@ -115,20 +117,6 @@ export class SQLite {
} catch (e) {
if (e instanceof Error) e.message += ` (query: ${sql})`;
throw e;
} finally {
// Since SQLite 3.48.0 (SQLite3MC v2.0.2) it's not possible to load fts5
// extensions before database has been decrypting. This is because
// executing a `SELECT` now accesses the underlying databases resulting in
// an error. Since FTS5 extensions depend on `SELECT fts5` to load the
// fts5 API, we must wait decrypt the database before we can load
// the extensions.
if (!this.extensionsLoaded && (await this.isDatabaseReady())) {
const betterTrigram = require("sqlite-better-trigram");
const fts5Html = require("sqlite3-fts5-html");
betterTrigram.load(this.sqlite);
fts5Html.load(this.sqlite);
this.extensionsLoaded = true;
}
}
}
@@ -156,22 +144,4 @@ export class SQLite {
retryDelay: 500
});
}
/**
* This just executes `SELECT 1` on the database to make sure its ready.
* On an encrypted database, this will fail until `PRAGMA key` has been
* called.
*/
private async isDatabaseReady() {
// return this.exec(`SELECT 1;`)
// .then(() => true)
// .catch(() => false);
if (!this.sqlite) return false;
try {
this.sqlite.prepare(`SELECT 1;`).run();
return true;
} catch {
return false;
}
}
}

View File

@@ -17,20 +17,26 @@ 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 React, { useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import BackupService from "../../services/backup";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eOpenRecoveryKeyDialog } from "../../utils/events";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -79,7 +85,8 @@ export const ChangePassword = () => {
context: "global"
});
setLoading(false);
Navigation.goBack();
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
@@ -101,6 +108,9 @@ export const ChangePassword = () => {
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader title={strings.changePassword()} />
<Seperator />
<Input
fwdRef={oldPasswordInputRef}
onChangeText={(value) => {
@@ -150,3 +160,9 @@ export const ChangePassword = () => {
</View>
);
};
ChangePassword.present = () => {
presentSheet({
component: <ChangePassword />
});
};

View File

@@ -39,7 +39,6 @@ import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { useLogin } from "./use-login";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
const LoginSteps = {
emailAuth: 1,
@@ -90,7 +89,7 @@ export const Login = ({ changeMode }) => {
return (
<>
<ForgotPassword />
<Dialog context="two_factor_verify" />
<SheetProvider context="two_factor_verify" />
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,

View File

@@ -24,8 +24,12 @@ import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { db } from "../../common/database/index";
import useTimer from "../../hooks/use-timer";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import { eCloseSimpleDialog } from "../../utils/events";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import { eCloseSheet } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
@@ -34,7 +38,6 @@ import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
const { colors } = useThemeColors();
@@ -43,7 +46,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
method: mfaInfo?.primaryMethod,
isPrimary: true
});
const { seconds, start, reset } = useTimer(currentMethod.method);
const { seconds, start } = useTimer(currentMethod.method);
const [loading, setLoading] = useState(false);
const inputRef = useRef();
const [sending, setSending] = useState(false);
@@ -59,7 +62,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
},
(result) => {
if (result) {
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
eSendEvent(eCloseSheet, "two_factor_verify");
}
setLoading(false);
}
@@ -130,12 +133,6 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
<ScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
paddingTop: 60
}}
>
<View
style={{
@@ -239,10 +236,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
<Button
title={strings.cancel()}
type="secondaryAccented"
onPress={() => {
reset();
onCancel();
}}
onPress={onCancel}
width={250}
/>
@@ -303,7 +297,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
};
TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
presentDialog({
presentSheet({
component: () => (
<TwoFactorVerification
onMfaLogin={onMfaLogin}
@@ -312,9 +306,7 @@ TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
/>
),
context: context || "two_factor_verify",
disableClosing: true,
transparent: false,
statusBarTranslucent: true
disableClosing: true
});
};

View File

@@ -24,7 +24,7 @@ import { clearMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSimpleDialog } from "../../utils/events";
import { eCloseSheet } from "../../utils/events";
import TwoFactorVerification from "./two-factor";
import { strings } from "@notesnook/intl";
@@ -92,7 +92,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
} catch (e) {
callback && callback(false);
if (e.message === "invalid_grant") {
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
eSendEvent(eCloseSheet, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}
@@ -100,7 +100,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
},
mfaInfo,
() => {
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
eSendEvent(eCloseSheet, "two_factor_verify");
setLoading(false);
setStep(LoginSteps.emailAuth);
}

View File

@@ -27,9 +27,6 @@ export type DialogInfo = {
paragraph?: string;
positiveText: string;
negativeText: string;
background?: string;
transparent?: boolean;
statusBarTranslucent?: boolean;
positivePress?: (...args: any[]) => Promise<any>;
onClose?: () => void;
positiveType?:

View File

@@ -130,17 +130,9 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
return visible && dialogInfo ? (
<BaseDialog
statusBarTranslucent={
dialogInfo.statusBarTranslucent === undefined
? false
: dialogInfo.statusBarTranslucent
}
statusBarTranslucent={false}
bounce={!dialogInfo.input}
closeOnTouch={!dialogInfo.disableBackdropClosing}
background={dialogInfo.background}
transparent={
dialogInfo.transparent === undefined ? true : dialogInfo.transparent
}
onShow={async () => {
if (dialogInfo.input) {
inputRef.current?.setNativeProps({

View File

@@ -16,22 +16,35 @@ 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, { useRef, useState } from "react";
import React, { RefObject, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { eSendEvent, ToastManager } from "../../../services/event-manager";
import {
eSendEvent,
presentSheet,
PresentSheetOptions,
ToastManager
} from "../../../services/event-manager";
import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { eUserLoggedIn } from "../../../utils/events";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import Input from "../../../components/ui/input";
import { Button } from "../../../components/ui/button";
type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
};
enum EmailChangeSteps {
verify,
changeEmail
}
export const ChangeEmail = () => {
export const ChangeEmail = ({ close }: ChangeEmailProps) => {
const [step, setStep] = useState(EmailChangeSteps.verify);
const emailChangeData = useRef<{
email?: string;
@@ -90,6 +103,10 @@ export const ChangeEmail = () => {
return (
<View style={{ paddingHorizontal: DefaultAppStyles.GAP }}>
<DialogHeader
title={strings.changeEmail()}
paragraph={strings.changeEmailDesc()}
/>
<View
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL
@@ -139,12 +156,23 @@ export const ChangeEmail = () => {
: strings.changeEmail()
}
type="accent"
style={{
width: "100%"
}}
width={250}
loading={loading}
onPress={onSubmit}
style={{
borderRadius: 100,
height: 45,
marginTop: 2
}}
/>
</View>
);
};
ChangeEmail.present = () => {
presentSheet({
component: (ref, close, update) => (
<ChangeEmail actionSheetRef={ref} close={close} update={update} />
)
});
};

View File

@@ -52,14 +52,7 @@ const useTimer = (initialId?: string) => {
};
}, [seconds, id]);
const reset = () => {
if (id) {
timers[id] = 0;
setSeconds(0);
}
};
return { seconds, setId, start, reset };
return { seconds, setId, start };
};
export default useTimer;

View File

@@ -43,8 +43,6 @@ import ThemeSelector from "./theme-selector";
import { TitleFormat } from "./title-format";
import { View } from "react-native";
import { DefaultAppStyles } from "../../utils/styles";
import { ChangePassword } from "../../components/auth/change-password";
import { ChangeEmail } from "./change-email";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
@@ -71,7 +69,5 @@ export const components: { [name: string]: ReactElement } = {
<AttachmentGroupProgress groupId="offline-mode" />
</View>
),
"sidebar-tab-selector": <SidebarTabPicker />,
"change-password": <ChangePassword />,
"change-email": <ChangeEmail />
"sidebar-tab-selector": <SidebarTabPicker />
};

View File

@@ -32,9 +32,11 @@ import { enabled } from "react-native-privacy-snapshot";
import ScreenGuardModule from "react-native-screenguard";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
import { endProgress, startProgress } from "../../components/dialogs/progress";
import { ChangeEmail } from "../../components/sheets/change-email";
import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
import { Progress } from "../../components/sheets/progress";
@@ -241,15 +243,17 @@ export const settingsGroups: SettingSection[] = [
{
id: "change-password",
name: strings.changePassword(),
type: "screen",
description: strings.changePasswordDesc(),
component: "change-password"
modifer: async () => {
ChangePassword.present();
},
description: strings.changePasswordDesc()
},
{
id: "change-email",
name: strings.changeEmail(),
type: "screen",
component: "change-email",
modifer: async () => {
ChangeEmail.present();
},
description: strings.changeEmailDesc()
},
{

View File

@@ -124,7 +124,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3058
versionCode 3057
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -3,8 +3,16 @@ package com.streetwriters.notesnook;
import com.facebook.react.ReactActivity;
import android.content.Intent;
import android.content.res.Configuration;
import androidx.core.graphics.Insets;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
@@ -22,6 +30,20 @@ public class MainActivity extends ReactActivity {
try {
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
} catch (Exception ignored) {}
if (Build.VERSION.SDK_INT >= 35) {
final View rootView = findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime());
rootView.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
);
return insets;
});
}
}
/**

View File

@@ -1091,7 +1091,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1165,7 +1165,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1196,7 +1196,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1270,7 +1270,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1429,7 +1429,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1441,7 +1441,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1472,7 +1472,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1485,7 +1485,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1515,7 +1515,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1589,7 +1589,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1620,7 +1620,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2136;
CURRENT_PROJECT_VERSION = 2135;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1695,7 +1695,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.2.2;
MARKETING_VERSION = 3.2.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.2.2",
"version": "3.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.2.2",
"version": "3.2.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.2.2",
"version": "3.2.1",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { store as noteStore } from "../stores/note-store";
import { store as trashStore } from "../stores/trash-store";
import { store as notebookStore } from "../stores/notebook-store";
import { store as attachmentStore } from "../stores/attachment-store";
import { store as reminderStore } from "../stores/reminder-store";
@@ -28,11 +27,7 @@ import { db } from "./db";
import { showToast } from "../utils/toast";
import Vault from "./vault";
import { TaskManager } from "./task-manager";
import {
ConfirmDialog,
showMultiDeleteConfirmation,
showMultiPermanentDeleteConfirmation
} from "../dialogs/confirm";
import { ConfirmDialog, showMultiDeleteConfirmation } from "../dialogs/confirm";
import { strings } from "@notesnook/intl";
async function moveNotesToTrash(ids: string[], confirm = true) {
@@ -82,7 +77,14 @@ async function moveNotebooksToTrash(ids: string[]) {
if (!result) return;
if (result.deleteContainingNotes) {
await Multiselect.moveNotesToTrash(await db.notebooks.notes(...ids), false);
await Multiselect.moveNotesToTrash(
Array.from(
new Set(
(await Promise.all(ids.map((id) => db.notebooks.notes(id)))).flat()
)
),
false
);
}
await TaskManager.startTask({
@@ -180,50 +182,10 @@ async function deleteTags(ids: string[]) {
showToast("success", strings.actions.deleted.tag(ids.length));
}
async function restoreItemsFromTrash(ids: string[]) {
if (!ids.length) return;
await TaskManager.startTask({
type: "status",
id: "restoreItems",
title: strings.inProgressActions.restoring.item(ids.length),
action: async (report) => {
report({
text: strings.inProgressActions.restoring.item(ids.length)
});
await trashStore.restore(...ids);
}
});
showToast("success", strings.actions.restored.item(ids.length));
}
async function deleteItemsFromTrash(ids: string[]) {
if (!ids.length) return;
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
await TaskManager.startTask({
type: "status",
id: "restoreItems",
title: strings.inProgressActions.permanentlyDeleting.item(ids.length),
action: async (report) => {
report({
text: strings.inProgressActions.permanentlyDeleting.item(ids.length)
});
await trashStore.delete(...ids);
}
});
showToast("success", strings.actions.permanentlyDeleted.item(ids.length));
}
export const Multiselect = {
moveRemindersToTrash,
moveNotebooksToTrash,
moveNotesToTrash,
deleteAttachments,
deleteTags,
restoreItemsFromTrash,
deleteItemsFromTrash
deleteTags
};

View File

@@ -561,16 +561,11 @@ export function Factory(Module) {
databases.add(db);
Module._sqlite3_free(zVfs);
check(fname, result);
return db;
};
})();
sqlite3.register_extensions = (function () {
return async function (db) {
Module.ccall("RegisterExtensionFunctions", "void", ["number"], [db]);
Module.ccall("sqlite3Fts5BetterTrigramInit", "void", ["number"], [db]);
Module.ccall("sqlite3Fts5HtmlInit", "void", ["number"], [db]);
check(fname, result);
return db;
};
})();

View File

@@ -724,12 +724,6 @@ export interface SQLiteAPI {
*/
open_v2(zFilename: string, iFlags?: number, zVfs?: string): Promise<number>;
/**
* Register and init extensions.
* @param db database pointer
*/
register_extensions(db: number): Promise<void>;
/**
* Compile an SQL statement
*

View File

@@ -227,9 +227,6 @@ class _SQLiteWorker {
}
async initialize() {
if (typeof this.db === "number")
await this.sqlite.register_extensions(this.db);
self.dispatchEvent(
new MessageEvent("message", {
data: { type: "databaseInitialized", dbName: this.name }

View File

@@ -142,12 +142,12 @@ a._sqlite3_table_column_metadata=function(){return(a._sqlite3_table_column_metad
a._sqlite3_test_control=function(){return(a._sqlite3_test_control=a.asm.Oe).apply(null,arguments)};a._sqlite3_create_filename=function(){return(a._sqlite3_create_filename=a.asm.Pe).apply(null,arguments)};a._sqlite3_free_filename=function(){return(a._sqlite3_free_filename=a.asm.Qe).apply(null,arguments)};a._sqlite3_uri_parameter=function(){return(a._sqlite3_uri_parameter=a.asm.Re).apply(null,arguments)};a._sqlite3_uri_key=function(){return(a._sqlite3_uri_key=a.asm.Se).apply(null,arguments)};
a._sqlite3_uri_boolean=function(){return(a._sqlite3_uri_boolean=a.asm.Te).apply(null,arguments)};a._sqlite3_uri_int64=function(){return(a._sqlite3_uri_int64=a.asm.Ue).apply(null,arguments)};a._sqlite3_filename_database=function(){return(a._sqlite3_filename_database=a.asm.Ve).apply(null,arguments)};a._sqlite3_filename_journal=function(){return(a._sqlite3_filename_journal=a.asm.We).apply(null,arguments)};a._sqlite3_filename_wal=function(){return(a._sqlite3_filename_wal=a.asm.Xe).apply(null,arguments)};
a._sqlite3_db_name=function(){return(a._sqlite3_db_name=a.asm.Ye).apply(null,arguments)};a._sqlite3_db_filename=function(){return(a._sqlite3_db_filename=a.asm.Ze).apply(null,arguments)};a._sqlite3_db_readonly=function(){return(a._sqlite3_db_readonly=a.asm._e).apply(null,arguments)};a._sqlite3_compileoption_used=function(){return(a._sqlite3_compileoption_used=a.asm.$e).apply(null,arguments)};a._sqlite3_compileoption_get=function(){return(a._sqlite3_compileoption_get=a.asm.af).apply(null,arguments)};
a._sqlite3_sourceid=function(){return(a._sqlite3_sourceid=a.asm.bf).apply(null,arguments)};var kd=a.___errno_location=function(){return(kd=a.___errno_location=a.asm.cf).apply(null,arguments)};a._sqlite3mc_config=function(){return(a._sqlite3mc_config=a.asm.df).apply(null,arguments)};a._sqlite3mc_cipher_count=function(){return(a._sqlite3mc_cipher_count=a.asm.ef).apply(null,arguments)};a._sqlite3mc_cipher_index=function(){return(a._sqlite3mc_cipher_index=a.asm.ff).apply(null,arguments)};
a._sqlite3mc_cipher_name=function(){return(a._sqlite3mc_cipher_name=a.asm.gf).apply(null,arguments)};a._sqlite3mc_config_cipher=function(){return(a._sqlite3mc_config_cipher=a.asm.hf).apply(null,arguments)};a._sqlite3mc_codec_data=function(){return(a._sqlite3mc_codec_data=a.asm.jf).apply(null,arguments)};a._sqlite3_key=function(){return(a._sqlite3_key=a.asm.kf).apply(null,arguments)};a._sqlite3_key_v2=function(){return(a._sqlite3_key_v2=a.asm.lf).apply(null,arguments)};
a._sqlite3_rekey_v2=function(){return(a._sqlite3_rekey_v2=a.asm.mf).apply(null,arguments)};a._sqlite3_rekey=function(){return(a._sqlite3_rekey=a.asm.nf).apply(null,arguments)};a._sqlite3_regexp_init=function(){return(a._sqlite3_regexp_init=a.asm.of).apply(null,arguments)};a._sqlite3mc_register_cipher=function(){return(a._sqlite3mc_register_cipher=a.asm.pf).apply(null,arguments)};
var Rb=a._malloc=function(){return(Rb=a._malloc=a.asm.qf).apply(null,arguments)},$c=a._free=function(){return($c=a._free=a.asm.rf).apply(null,arguments)};a._RegisterExtensionFunctions=function(){return(a._RegisterExtensionFunctions=a.asm.sf).apply(null,arguments)};a._sqlite3Fts5BetterTrigramInit=function(){return(a._sqlite3Fts5BetterTrigramInit=a.asm.tf).apply(null,arguments)};a._sqlite3Fts5HtmlInit=function(){return(a._sqlite3Fts5HtmlInit=a.asm.uf).apply(null,arguments)};
a._set_authorizer=function(){return(a._set_authorizer=a.asm.vf).apply(null,arguments)};a._create_function=function(){return(a._create_function=a.asm.wf).apply(null,arguments)};a._create_module=function(){return(a._create_module=a.asm.xf).apply(null,arguments)};a._progress_handler=function(){return(a._progress_handler=a.asm.yf).apply(null,arguments)};a._register_vfs=function(){return(a._register_vfs=a.asm.zf).apply(null,arguments)};
a._getSqliteFree=function(){return(a._getSqliteFree=a.asm.Af).apply(null,arguments)};a._main=function(){return(a._main=a.asm.Bf).apply(null,arguments)};
a._sqlite3_sourceid=function(){return(a._sqlite3_sourceid=a.asm.bf).apply(null,arguments)};a._sqlite3mc_config=function(){return(a._sqlite3mc_config=a.asm.cf).apply(null,arguments)};a._sqlite3mc_cipher_count=function(){return(a._sqlite3mc_cipher_count=a.asm.df).apply(null,arguments)};a._sqlite3mc_cipher_index=function(){return(a._sqlite3mc_cipher_index=a.asm.ef).apply(null,arguments)};a._sqlite3mc_cipher_name=function(){return(a._sqlite3mc_cipher_name=a.asm.ff).apply(null,arguments)};
a._sqlite3mc_config_cipher=function(){return(a._sqlite3mc_config_cipher=a.asm.gf).apply(null,arguments)};a._sqlite3mc_codec_data=function(){return(a._sqlite3mc_codec_data=a.asm.hf).apply(null,arguments)};a._sqlite3_key=function(){return(a._sqlite3_key=a.asm.jf).apply(null,arguments)};a._sqlite3_key_v2=function(){return(a._sqlite3_key_v2=a.asm.kf).apply(null,arguments)};a._sqlite3_rekey_v2=function(){return(a._sqlite3_rekey_v2=a.asm.lf).apply(null,arguments)};
a._sqlite3_rekey=function(){return(a._sqlite3_rekey=a.asm.mf).apply(null,arguments)};a._sqlite3_regexp_init=function(){return(a._sqlite3_regexp_init=a.asm.nf).apply(null,arguments)};a._sqlite3mc_register_cipher=function(){return(a._sqlite3mc_register_cipher=a.asm.of).apply(null,arguments)};
var kd=a.___errno_location=function(){return(kd=a.___errno_location=a.asm.pf).apply(null,arguments)},Rb=a._malloc=function(){return(Rb=a._malloc=a.asm.qf).apply(null,arguments)},$c=a._free=function(){return($c=a._free=a.asm.rf).apply(null,arguments)};a._RegisterExtensionFunctions=function(){return(a._RegisterExtensionFunctions=a.asm.sf).apply(null,arguments)};a._sqlite3Fts5BetterTrigramInit=function(){return(a._sqlite3Fts5BetterTrigramInit=a.asm.tf).apply(null,arguments)};
a._sqlite3Fts5HtmlInit=function(){return(a._sqlite3Fts5HtmlInit=a.asm.uf).apply(null,arguments)};a._set_authorizer=function(){return(a._set_authorizer=a.asm.vf).apply(null,arguments)};a._create_function=function(){return(a._create_function=a.asm.wf).apply(null,arguments)};a._create_module=function(){return(a._create_module=a.asm.xf).apply(null,arguments)};a._progress_handler=function(){return(a._progress_handler=a.asm.yf).apply(null,arguments)};
a._register_vfs=function(){return(a._register_vfs=a.asm.zf).apply(null,arguments)};a._getSqliteFree=function(){return(a._getSqliteFree=a.asm.Af).apply(null,arguments)};a._main=function(){return(a._main=a.asm.Bf).apply(null,arguments)};
var ab=a._emscripten_builtin_memalign=function(){return(ab=a._emscripten_builtin_memalign=a.asm.Cf).apply(null,arguments)},md=a.getTempRet0=function(){return(md=a.getTempRet0=a.asm.Ef).apply(null,arguments)},hd=a.stackSave=function(){return(hd=a.stackSave=a.asm.Ff).apply(null,arguments)},fd=a.stackRestore=function(){return(fd=a.stackRestore=a.asm.Gf).apply(null,arguments)},gd=a.stackAlloc=function(){return(gd=a.stackAlloc=a.asm.Hf).apply(null,arguments)},Yc=a._asyncify_start_unwind=function(){return(Yc=
a._asyncify_start_unwind=a.asm.If).apply(null,arguments)},Mc=a._asyncify_stop_unwind=function(){return(Mc=a._asyncify_stop_unwind=a.asm.Jf).apply(null,arguments)},Wc=a._asyncify_start_rewind=function(){return(Wc=a._asyncify_start_rewind=a.asm.Kf).apply(null,arguments)},Zc=a._asyncify_stop_rewind=function(){return(Zc=a._asyncify_stop_rewind=a.asm.Lf).apply(null,arguments)};a._sqlite3_version=3232;a.UTF8ToString=x;a.stringToUTF8=qa;a.lengthBytesUTF8=ra;a.getTempRet0=md;a.ccall=Z;
a.cwrap=function(b,c,d,e){d=d||[];var f=d.every(g=>"number"===g||"boolean"===g);return"string"!==c&&f&&!e?a["_"+b]:function(){return Z(b,c,d,arguments,e)}};

View File

@@ -137,9 +137,9 @@ a._sqlite3_table_column_metadata=function(){return(a._sqlite3_table_column_metad
a._sqlite3_test_control=function(){return(a._sqlite3_test_control=a.asm.Oe).apply(null,arguments)};a._sqlite3_create_filename=function(){return(a._sqlite3_create_filename=a.asm.Pe).apply(null,arguments)};a._sqlite3_free_filename=function(){return(a._sqlite3_free_filename=a.asm.Qe).apply(null,arguments)};a._sqlite3_uri_parameter=function(){return(a._sqlite3_uri_parameter=a.asm.Re).apply(null,arguments)};a._sqlite3_uri_key=function(){return(a._sqlite3_uri_key=a.asm.Se).apply(null,arguments)};
a._sqlite3_uri_boolean=function(){return(a._sqlite3_uri_boolean=a.asm.Te).apply(null,arguments)};a._sqlite3_uri_int64=function(){return(a._sqlite3_uri_int64=a.asm.Ue).apply(null,arguments)};a._sqlite3_filename_database=function(){return(a._sqlite3_filename_database=a.asm.Ve).apply(null,arguments)};a._sqlite3_filename_journal=function(){return(a._sqlite3_filename_journal=a.asm.We).apply(null,arguments)};a._sqlite3_filename_wal=function(){return(a._sqlite3_filename_wal=a.asm.Xe).apply(null,arguments)};
a._sqlite3_db_name=function(){return(a._sqlite3_db_name=a.asm.Ye).apply(null,arguments)};a._sqlite3_db_filename=function(){return(a._sqlite3_db_filename=a.asm.Ze).apply(null,arguments)};a._sqlite3_db_readonly=function(){return(a._sqlite3_db_readonly=a.asm._e).apply(null,arguments)};a._sqlite3_compileoption_used=function(){return(a._sqlite3_compileoption_used=a.asm.$e).apply(null,arguments)};a._sqlite3_compileoption_get=function(){return(a._sqlite3_compileoption_get=a.asm.af).apply(null,arguments)};
a._sqlite3_sourceid=function(){return(a._sqlite3_sourceid=a.asm.bf).apply(null,arguments)};var Nc=a.___errno_location=function(){return(Nc=a.___errno_location=a.asm.cf).apply(null,arguments)};a._sqlite3mc_config=function(){return(a._sqlite3mc_config=a.asm.df).apply(null,arguments)};a._sqlite3mc_cipher_count=function(){return(a._sqlite3mc_cipher_count=a.asm.ef).apply(null,arguments)};a._sqlite3mc_cipher_index=function(){return(a._sqlite3mc_cipher_index=a.asm.ff).apply(null,arguments)};
a._sqlite3mc_cipher_name=function(){return(a._sqlite3mc_cipher_name=a.asm.gf).apply(null,arguments)};a._sqlite3mc_config_cipher=function(){return(a._sqlite3mc_config_cipher=a.asm.hf).apply(null,arguments)};a._sqlite3mc_codec_data=function(){return(a._sqlite3mc_codec_data=a.asm.jf).apply(null,arguments)};a._sqlite3_key=function(){return(a._sqlite3_key=a.asm.kf).apply(null,arguments)};a._sqlite3_key_v2=function(){return(a._sqlite3_key_v2=a.asm.lf).apply(null,arguments)};
a._sqlite3_rekey_v2=function(){return(a._sqlite3_rekey_v2=a.asm.mf).apply(null,arguments)};a._sqlite3_rekey=function(){return(a._sqlite3_rekey=a.asm.nf).apply(null,arguments)};a._sqlite3_regexp_init=function(){return(a._sqlite3_regexp_init=a.asm.of).apply(null,arguments)};a._sqlite3mc_register_cipher=function(){return(a._sqlite3mc_register_cipher=a.asm.pf).apply(null,arguments)};var Pb=a._malloc=function(){return(Pb=a._malloc=a.asm.qf).apply(null,arguments)};
a._sqlite3_sourceid=function(){return(a._sqlite3_sourceid=a.asm.bf).apply(null,arguments)};a._sqlite3mc_config=function(){return(a._sqlite3mc_config=a.asm.cf).apply(null,arguments)};a._sqlite3mc_cipher_count=function(){return(a._sqlite3mc_cipher_count=a.asm.df).apply(null,arguments)};a._sqlite3mc_cipher_index=function(){return(a._sqlite3mc_cipher_index=a.asm.ef).apply(null,arguments)};a._sqlite3mc_cipher_name=function(){return(a._sqlite3mc_cipher_name=a.asm.ff).apply(null,arguments)};
a._sqlite3mc_config_cipher=function(){return(a._sqlite3mc_config_cipher=a.asm.gf).apply(null,arguments)};a._sqlite3mc_codec_data=function(){return(a._sqlite3mc_codec_data=a.asm.hf).apply(null,arguments)};a._sqlite3_key=function(){return(a._sqlite3_key=a.asm.jf).apply(null,arguments)};a._sqlite3_key_v2=function(){return(a._sqlite3_key_v2=a.asm.kf).apply(null,arguments)};a._sqlite3_rekey_v2=function(){return(a._sqlite3_rekey_v2=a.asm.lf).apply(null,arguments)};
a._sqlite3_rekey=function(){return(a._sqlite3_rekey=a.asm.mf).apply(null,arguments)};a._sqlite3_regexp_init=function(){return(a._sqlite3_regexp_init=a.asm.nf).apply(null,arguments)};a._sqlite3mc_register_cipher=function(){return(a._sqlite3mc_register_cipher=a.asm.of).apply(null,arguments)};var Nc=a.___errno_location=function(){return(Nc=a.___errno_location=a.asm.pf).apply(null,arguments)},Pb=a._malloc=function(){return(Pb=a._malloc=a.asm.qf).apply(null,arguments)};
a._free=function(){return(a._free=a.asm.rf).apply(null,arguments)};a._RegisterExtensionFunctions=function(){return(a._RegisterExtensionFunctions=a.asm.sf).apply(null,arguments)};a._sqlite3Fts5BetterTrigramInit=function(){return(a._sqlite3Fts5BetterTrigramInit=a.asm.tf).apply(null,arguments)};a._sqlite3Fts5HtmlInit=function(){return(a._sqlite3Fts5HtmlInit=a.asm.uf).apply(null,arguments)};a._set_authorizer=function(){return(a._set_authorizer=a.asm.vf).apply(null,arguments)};
a._create_function=function(){return(a._create_function=a.asm.wf).apply(null,arguments)};a._create_module=function(){return(a._create_module=a.asm.xf).apply(null,arguments)};a._progress_handler=function(){return(a._progress_handler=a.asm.yf).apply(null,arguments)};a._register_vfs=function(){return(a._register_vfs=a.asm.zf).apply(null,arguments)};a._getSqliteFree=function(){return(a._getSqliteFree=a.asm.Af).apply(null,arguments)};a._main=function(){return(a._main=a.asm.Bf).apply(null,arguments)};
var Za=a._emscripten_builtin_memalign=function(){return(Za=a._emscripten_builtin_memalign=a.asm.Cf).apply(null,arguments)},Pc=a.getTempRet0=function(){return(Pc=a.getTempRet0=a.asm.Ef).apply(null,arguments)},Kc=a.stackSave=function(){return(Kc=a.stackSave=a.asm.Ff).apply(null,arguments)},Lc=a.stackRestore=function(){return(Lc=a.stackRestore=a.asm.Gf).apply(null,arguments)},Jc=a.stackAlloc=function(){return(Jc=a.stackAlloc=a.asm.Hf).apply(null,arguments)};a._sqlite3_version=3232;a.UTF8ToString=v;

View File

@@ -19,15 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import ListItem from "../list-item";
import { Restore, DeleteForver } from "../icons";
import { store } from "../../stores/trash-store";
import { Flex, Text } from "@theme-ui/components";
import TimeAgo from "../time-ago";
import { toTitleCase } from "@notesnook/common";
import { pluralize, toTitleCase } from "@notesnook/common";
import { showToast } from "../../utils/toast";
import { MenuItem } from "@notesnook/ui";
import { TrashItem as TrashItemType } from "@notesnook/core";
import { useEditorStore } from "../../stores/editor-store";
import { showMultiPermanentDeleteConfirmation } from "../../dialogs/confirm";
import { useStore as useSelectionStore } from "../../stores/selection-store";
import { strings } from "@notesnook/intl";
import { Multiselect } from "../../common/multi-select";
type TrashItemProps = { item: TrashItemType; date: number };
function TrashItem(props: TrashItemProps) {
@@ -42,9 +44,7 @@ function TrashItem(props: TrashItemProps) {
body={item.itemType === "note" ? item.headline : item.description}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await Multiselect.deleteItemsFromTrash(
useSelectionStore.getState().selectedItems
);
await deleteTrash(useSelectionStore.getState().selectedItems);
}
}}
footer={
@@ -81,7 +81,9 @@ export const trashMenuItems: (
key: "restore",
title: strings.restore(),
icon: Restore.path,
onClick: () => Multiselect.restoreItemsFromTrash(ids),
onClick: async () => {
await store.restore(...ids);
},
multiSelect: true
},
{
@@ -90,8 +92,16 @@ export const trashMenuItems: (
title: strings.delete(),
icon: DeleteForver.path,
variant: "dangerous",
onClick: () => Multiselect.deleteItemsFromTrash(ids),
onClick: async () => {
await deleteTrash(ids);
},
multiSelect: true
}
];
};
export async function deleteTrash(ids: string[]) {
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
await store.delete(...ids);
showToast("success", `${pluralize(ids.length, "item")} permanently deleted`);
}

View File

@@ -1,5 +1,5 @@
- New and improved search with highlighting
- Fixed app crashing on android 16 devices
- Many bug fixes and improvements
- You can now share multiple files to Notesnook
- Fix file and image sharing not working
- Many other bug fixes and small improvements
Thank you for using Notesnook!

View File

@@ -1,5 +0,0 @@
- New and improved search with highlighting
- Fixed app crashing on android 16 devices
- Many bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -48,12 +48,11 @@ export async function resolveItems(ids: string[], items: Item[]) {
const { type } = items[0];
if (type === "note") return resolveNotes(ids);
else if (type === "notebook") {
return await db.notebooks.totalNotes(...ids);
return Promise.all(ids.map((id) => db.notebooks.totalNotes(id)));
} else if (type === "tag") {
const relations = await db.relations
.from({ type: "tag", ids }, "note")
.get();
return ids.map((id) => relations.filter((r) => r.fromId === id).length);
return Promise.all(
ids.map((id) => db.relations.from({ id, type: "tag" }, "note").count())
);
}
return [];
}

View File

@@ -99,8 +99,8 @@ test("delete note", () =>
await db.notes.moveToTrash(id);
expect(await db.notes.note(id)).toBeUndefined();
expect(await db.notebooks.totalNotes(notebookId)).toStrictEqual([0]);
expect(await db.notebooks.totalNotes(subNotebookId)).toStrictEqual([0]);
expect(await db.notebooks.totalNotes(notebookId)).toBe(0);
expect(await db.notebooks.totalNotes(subNotebookId)).toBe(0);
}));
test("get all notes", () =>
@@ -323,8 +323,8 @@ test("add note to subnotebook", () =>
.from({ type: "notebook", id: notebookId }, "notebook")
.count()
).toBe(1);
expect(await db.notebooks.totalNotes(subNotebookId)).toStrictEqual([1]);
expect(await db.notebooks.totalNotes(notebookId)).toStrictEqual([1]);
expect(await db.notebooks.totalNotes(subNotebookId)).toBe(1);
expect(await db.notebooks.totalNotes(notebookId)).toBe(1);
}));
test("duplicate note to topic should not be added", () =>
@@ -333,7 +333,7 @@ test("duplicate note to topic should not be added", () =>
notebookTitle: "Hello",
subNotebookTitle: "Home"
});
expect(await db.notebooks.totalNotes(subNotebookId)).toStrictEqual([1]);
expect(await db.notebooks.totalNotes(subNotebookId)).toBe(1);
}));
test("add the same note to 2 notebooks", () =>

View File

@@ -22,8 +22,8 @@ import Database from "../api/index.js";
import { Notebook, TrashOrItem, isTrashItem } from "../types.js";
import { ICollection } from "./collection.js";
import { SQLCollection } from "../database/sql-collection.js";
import { DatabaseSchema, isFalse } from "../database/index.js";
import { Kysely, sql, Transaction } from "@streetwriters/kysely";
import { isFalse } from "../database/index.js";
import { sql } from "@streetwriters/kysely";
import { deleteItems } from "../utils/array.js";
import {
CHECK_IDS,
@@ -125,45 +125,65 @@ export class Notebooks implements ICollection {
await this.collection.update(ids, { pinned: state });
}
async totalNotes(...ids: string[]) {
const result = await withSubNotebooks(
this.db.sql(),
ids,
this.db.trash.cache.notebooks
)
async totalNotes(id: string) {
const result = await this.db
.sql()
.withRecursive(`subNotebooks(id)`, (eb) =>
eb
.selectNoFrom((eb) => eb.val(id).as("id"))
.unionAll((eb) =>
eb
.selectFrom(["relations", "subNotebooks"])
.select("relations.toId as id")
.where("toType", "==", "notebook")
.where("fromType", "==", "notebook")
.whereRef("fromId", "==", "subNotebooks.id")
.where("toId", "not in", this.db.trash.cache.notebooks)
.$narrowType<{ id: string }>()
)
)
.selectFrom("relations")
.innerJoin("subNotebooks", "subNotebooks.id", "relations.fromId")
.where("toType", "==", "note")
.where("fromType", "==", "notebook")
.where("fromId", "in", (eb) =>
eb.selectFrom("subNotebooks").select("subNotebooks.id")
)
.where("toId", "not in", this.db.trash.cache.notes)
.select((eb) => [
"subNotebooks.rootId as id",
eb.fn.count<number>("relations.toId").distinct().as("totalNotes")
])
.groupBy("subNotebooks.rootId")
.execute();
.select((eb) => eb.fn.count<number>("relations.toId").as("totalNotes"))
.executeTakeFirst();
return ids.map((id) => {
const item = result.find((i) => i.id === id);
return item ? item.totalNotes : 0;
});
if (!result) return 0;
return result.totalNotes;
}
async notes(...ids: string[]) {
const result = await withSubNotebooks(
this.db.sql(),
ids,
this.db.trash.cache.notebooks
)
async notes(id: string) {
const result = await this.db
.sql()
.withRecursive(`subNotebooks(id)`, (eb) =>
eb
.selectNoFrom((eb) => eb.val(id).as("id"))
.unionAll((eb) =>
eb
.selectFrom(["relations", "subNotebooks"])
.select("relations.toId as id")
.where("toType", "==", "notebook")
.where("fromType", "==", "notebook")
.whereRef("fromId", "==", "subNotebooks.id")
.where("toId", "not in", this.db.trash.cache.notebooks)
.$narrowType<{ id: string }>()
)
)
.selectFrom("relations")
.innerJoin("subNotebooks", "subNotebooks.id", "relations.fromId")
.where("toType", "==", "note")
.where("fromType", "==", "notebook")
.where("fromId", "in", (eb) =>
eb.selectFrom("subNotebooks").select("subNotebooks.id")
)
.where("toId", "not in", this.db.trash.cache.notes)
.select("relations.toId as id")
.distinct()
.$narrowType<{ id: string }>()
.execute();
return result.map((i) => i.id);
}
@@ -234,7 +254,26 @@ export class Notebooks implements ICollection {
async moveToTrash(...ids: string[]) {
await this.db.transaction(async (tr) => {
const query = withSubNotebooks(tr, ids, this.db.trash.cache.notebooks)
const query = tr
.withRecursive(`subNotebooks(id)`, (eb) =>
eb
.selectFrom(() =>
sql<{ id: string }>`(VALUES ${sql.join(
ids.map((id) => sql.raw(`('${id}')`))
)})`.as("roots")
)
.selectAll()
.unionAll((eb) =>
eb
.selectFrom(["relations", "subNotebooks"])
.select("relations.toId as id")
.where("toType", "==", "notebook")
.where("fromType", "==", "notebook")
.whereRef("fromId", "==", "subNotebooks.id")
.where("toId", "not in", this.db.trash.cache.notebooks)
.$narrowType<{ id: string }>()
)
)
.selectFrom("subNotebooks")
.select("id");
@@ -266,45 +305,3 @@ export class Notebooks implements ICollection {
return relation[0]?.fromId;
}
}
export function withSubNotebooks(
db: Kysely<DatabaseSchema> | Transaction<DatabaseSchema>,
ids: string[],
excluded: string[]
) {
return db.withRecursive(`subNotebooks(id, path, rootId)`, (eb) =>
eb
.selectFrom(() =>
sql<{
id: string;
path: string;
rootId: string;
}>`(VALUES ${sql.join(
ids.map((id) => sql.raw(`('${id}', '${id}', '${id}')`))
)})`.as("roots")
)
.selectAll()
.unionAll((eb) =>
eb
.selectFrom(["relations", "subNotebooks"])
.select([
"relations.toId as id",
// Concatenate parent path with current id
sql<string>`subNotebooks.path || '/' || relations.toId`.as("path"),
// Preserve original root
"subNotebooks.rootId as rootId"
])
.where("toType", "==", "notebook")
.where("fromType", "==", "notebook")
.whereRef("fromId", "==", "subNotebooks.id")
.where("toId", "not in", excluded)
// Use path to prevent cycles
.where(
"subNotebooks.path",
"not like",
sql`'%' || relations.toId || '%'`
)
.$narrowType<{ id: string; path: string; rootId: string }>()
)
);
}

View File

@@ -34,7 +34,6 @@ import {
checkIsUserPremium,
FREE_NOTEBOOKS_LIMIT
} from "../common.js";
import { withSubNotebooks } from "./notebooks.js";
export default class Trash {
collections = ["notes", "notebooks"] as const;
@@ -325,11 +324,27 @@ export default class Trash {
}
private async subNotebooks(notebookIds: string[]) {
const ids = await withSubNotebooks(
this.db.sql(),
notebookIds,
this.userDeletedCache.notebooks
)
const ids = await this.db
.sql()
.withRecursive(`subNotebooks(id)`, (eb) =>
eb
.selectFrom((eb) =>
sql<{ id: string }>`(VALUES ${sql.join(
notebookIds.map((id) => eb.parens(sql`${id}`))
)})`.as("notebookIds")
)
.selectAll()
.unionAll((eb) =>
eb
.selectFrom(["relations", "subNotebooks"])
.select("relations.toId as id")
.where("toType", "==", "notebook")
.where("fromType", "==", "notebook")
.whereRef("fromId", "==", "subNotebooks.id")
.where("toId", "not in", this.userDeletedCache.notebooks)
.$narrowType<{ id: string }>()
)
)
.selectFrom("subNotebooks")
.select("id")
.where("id", "not in", notebookIds)

View File

@@ -1,11 +1,17 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
"POT-Creation-Date: 2025-06-05 11:43+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/strings.ts:2410
msgid " \"Notebook > Notes\""
@@ -151,7 +157,7 @@ msgstr "{count, plural, one {Attachment deleted} other {# attachments deleted}}"
msgid "{count, plural, one {Attachment downloaded at {path}} other {#/{total} attachments downloaded as a zip file at {path}}}"
msgstr "{count, plural, one {Attachment downloaded at {path}} other {#/{total} attachments downloaded as a zip file at {path}}}"
#: generated/actions.ts:216
#: generated/actions.ts:210
msgid "{count, plural, one {Color renamed} other {# colors renamed}}"
msgstr "{count, plural, one {Color renamed} other {# colors renamed}}"
@@ -244,7 +250,7 @@ msgstr "{count, plural, one {Item could not be published} other {# items could n
msgid "{count, plural, one {Item could not be unpublished} other {# items could not be unpublished}}"
msgstr "{count, plural, one {Item could not be unpublished} other {# items could not be unpublished}}"
#: generated/actions.ts:208
#: generated/actions.ts:202
msgid "{count, plural, one {Item created} other {# items created}}"
msgstr "{count, plural, one {Item created} other {# items created}}"
@@ -252,7 +258,7 @@ msgstr "{count, plural, one {Item created} other {# items created}}"
msgid "{count, plural, one {Item deleted} other {# items deleted}}"
msgstr "{count, plural, one {Item deleted} other {# items deleted}}"
#: generated/actions.ts:179
#: generated/actions.ts:173
msgid "{count, plural, one {Item edited} other {# items edited}}"
msgstr "{count, plural, one {Item edited} other {# items edited}}"
@@ -260,25 +266,24 @@ msgstr "{count, plural, one {Item edited} other {# items edited}}"
msgid "{count, plural, one {Item moved to trash} other {# items moved to trash}}"
msgstr "{count, plural, one {Item moved to trash} other {# items moved to trash}}"
#: generated/actions.ts:80
#: generated/actions.ts:93
#: generated/actions.ts:87
msgid "{count, plural, one {Item permanently deleted} other {# items permanently deleted}}"
msgstr "{count, plural, one {Item permanently deleted} other {# items permanently deleted}}"
#: generated/actions.ts:110
#: generated/actions.ts:104
msgid "{count, plural, one {Item published} other {# items published}}"
msgstr "{count, plural, one {Item published} other {# items published}}"
#: generated/actions.ts:225
#: generated/actions.ts:219
msgid "{count, plural, one {Item renamed} other {# items renamed}}"
msgstr "{count, plural, one {Item renamed} other {# items renamed}}"
#: generated/actions.ts:143
#: generated/actions.ts:156
#: generated/actions.ts:137
#: generated/actions.ts:150
msgid "{count, plural, one {Item restored} other {# items restored}}"
msgstr "{count, plural, one {Item restored} other {# items restored}}"
#: generated/actions.ts:127
#: generated/actions.ts:121
msgid "{count, plural, one {Item unpublished} other {# items unpublished}}"
msgstr "{count, plural, one {Item unpublished} other {# items unpublished}}"
@@ -318,15 +323,15 @@ msgstr "{count, plural, one {Note moved to trash} other {# notes moved to trash}
msgid "{count, plural, one {Note permanently deleted} other {# notes permanently deleted}}"
msgstr "{count, plural, one {Note permanently deleted} other {# notes permanently deleted}}"
#: generated/actions.ts:101
#: generated/actions.ts:95
msgid "{count, plural, one {Note published} other {# notes published}}"
msgstr "{count, plural, one {Note published} other {# notes published}}"
#: generated/actions.ts:135
#: generated/actions.ts:129
msgid "{count, plural, one {Note restored} other {# notes restored}}"
msgstr "{count, plural, one {Note restored} other {# notes restored}}"
#: generated/actions.ts:118
#: generated/actions.ts:112
msgid "{count, plural, one {Note unpublished} other {# notes unpublished}}"
msgstr "{count, plural, one {Note unpublished} other {# notes unpublished}}"
@@ -334,7 +339,7 @@ msgstr "{count, plural, one {Note unpublished} other {# notes unpublished}}"
msgid "{count, plural, one {Note will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?} other {# notes will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?}}"
msgstr "{count, plural, one {Note will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?} other {# notes will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?}}"
#: generated/actions.ts:187
#: generated/actions.ts:181
msgid "{count, plural, one {Notebook created} other {# notebooks created}}"
msgstr "{count, plural, one {Notebook created} other {# notebooks created}}"
@@ -342,7 +347,7 @@ msgstr "{count, plural, one {Notebook created} other {# notebooks created}}"
msgid "{count, plural, one {Notebook deleted} other {# notebooks deleted}}"
msgstr "{count, plural, one {Notebook deleted} other {# notebooks deleted}}"
#: generated/actions.ts:168
#: generated/actions.ts:162
msgid "{count, plural, one {Notebook edited} other {# notebooks edited}}"
msgstr "{count, plural, one {Notebook edited} other {# notebooks edited}}"
@@ -354,7 +359,7 @@ msgstr "{count, plural, one {Notebook moved to trash} other {# notebooks moved t
msgid "{count, plural, one {Notebook permanently deleted} other {# notebooks permanently deleted}}"
msgstr "{count, plural, one {Notebook permanently deleted} other {# notebooks permanently deleted}}"
#: generated/actions.ts:139
#: generated/actions.ts:133
msgid "{count, plural, one {Notebook restored} other {# notebooks restored}}"
msgstr "{count, plural, one {Notebook restored} other {# notebooks restored}}"
@@ -367,11 +372,6 @@ msgstr "{count, plural, one {Permanently delete attachment} other {Permanently d
msgid "{count, plural, one {Permanently delete item} other {Permanently delete # items}}"
msgstr "{count, plural, one {Permanently delete item} other {Permanently delete # items}}"
#: generated/in-progress-actions.ts:66
#: generated/in-progress-actions.ts:75
msgid "{count, plural, one {permanently deleting item...} other {permanently deleting # items...}}"
msgstr "{count, plural, one {permanently deleting item...} other {permanently deleting # items...}}"
#: generated/do-actions.ts:99
msgid "{count, plural, one {Pin item} other {Pin # items}}"
msgstr "{count, plural, one {Pin item} other {Pin # items}}"
@@ -444,16 +444,11 @@ msgstr "{count, plural, one {Restore note} other {Restore # notes}}"
msgid "{count, plural, one {Restore notebook} other {Restore # notebooks}}"
msgstr "{count, plural, one {Restore notebook} other {Restore # notebooks}}"
#: generated/in-progress-actions.ts:49
#: generated/in-progress-actions.ts:58
msgid "{count, plural, one {Restoring item...} other {Restoring # items...}}"
msgstr "{count, plural, one {Restoring item...} other {Restoring # items...}}"
#: generated/actions.ts:195
#: generated/actions.ts:189
msgid "{count, plural, one {Shortcut created} other {# shortcuts created}}"
msgstr "{count, plural, one {Shortcut created} other {# shortcuts created}}"
#: generated/actions.ts:191
#: generated/actions.ts:185
msgid "{count, plural, one {Tag created} other {# tags created}}"
msgstr "{count, plural, one {Tag created} other {# tags created}}"
@@ -461,7 +456,7 @@ msgstr "{count, plural, one {Tag created} other {# tags created}}"
msgid "{count, plural, one {Tag deleted} other {# tags deleted}}"
msgstr "{count, plural, one {Tag deleted} other {# tags deleted}}"
#: generated/actions.ts:164
#: generated/actions.ts:158
msgid "{count, plural, one {Tag edited} other {# tags edited}}"
msgstr "{count, plural, one {Tag edited} other {# tags edited}}"
@@ -618,8 +613,8 @@ msgid "A to Z"
msgstr "A to Z"
#: src/strings.ts:447
msgid "A vault stores your notes in an encrypted storage."
msgstr "A vault stores your notes in an encrypted storage."
msgid "A vault stores your notes in a encrypted storage."
msgstr "A vault stores your notes in a encrypted storage."
#: src/strings.ts:661
msgid "Abc"

View File

@@ -1,11 +1,17 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
"POT-Creation-Date: 2025-06-05 11:43+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: pseudo-LOCALE\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/strings.ts:2410
msgid " \"Notebook > Notes\""
@@ -151,7 +157,7 @@ msgstr ""
msgid "{count, plural, one {Attachment downloaded at {path}} other {#/{total} attachments downloaded as a zip file at {path}}}"
msgstr ""
#: generated/actions.ts:216
#: generated/actions.ts:210
msgid "{count, plural, one {Color renamed} other {# colors renamed}}"
msgstr ""
@@ -244,7 +250,7 @@ msgstr ""
msgid "{count, plural, one {Item could not be unpublished} other {# items could not be unpublished}}"
msgstr ""
#: generated/actions.ts:208
#: generated/actions.ts:202
msgid "{count, plural, one {Item created} other {# items created}}"
msgstr ""
@@ -252,7 +258,7 @@ msgstr ""
msgid "{count, plural, one {Item deleted} other {# items deleted}}"
msgstr ""
#: generated/actions.ts:179
#: generated/actions.ts:173
msgid "{count, plural, one {Item edited} other {# items edited}}"
msgstr ""
@@ -260,25 +266,24 @@ msgstr ""
msgid "{count, plural, one {Item moved to trash} other {# items moved to trash}}"
msgstr ""
#: generated/actions.ts:80
#: generated/actions.ts:93
#: generated/actions.ts:87
msgid "{count, plural, one {Item permanently deleted} other {# items permanently deleted}}"
msgstr ""
#: generated/actions.ts:110
#: generated/actions.ts:104
msgid "{count, plural, one {Item published} other {# items published}}"
msgstr ""
#: generated/actions.ts:225
#: generated/actions.ts:219
msgid "{count, plural, one {Item renamed} other {# items renamed}}"
msgstr ""
#: generated/actions.ts:143
#: generated/actions.ts:156
#: generated/actions.ts:137
#: generated/actions.ts:150
msgid "{count, plural, one {Item restored} other {# items restored}}"
msgstr ""
#: generated/actions.ts:127
#: generated/actions.ts:121
msgid "{count, plural, one {Item unpublished} other {# items unpublished}}"
msgstr ""
@@ -318,15 +323,15 @@ msgstr ""
msgid "{count, plural, one {Note permanently deleted} other {# notes permanently deleted}}"
msgstr ""
#: generated/actions.ts:101
#: generated/actions.ts:95
msgid "{count, plural, one {Note published} other {# notes published}}"
msgstr ""
#: generated/actions.ts:135
#: generated/actions.ts:129
msgid "{count, plural, one {Note restored} other {# notes restored}}"
msgstr ""
#: generated/actions.ts:118
#: generated/actions.ts:112
msgid "{count, plural, one {Note unpublished} other {# notes unpublished}}"
msgstr ""
@@ -334,7 +339,7 @@ msgstr ""
msgid "{count, plural, one {Note will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?} other {# notes will be automatically deleted from all other devices & any future changes won't get synced. Are you sure you want to continue?}}"
msgstr ""
#: generated/actions.ts:187
#: generated/actions.ts:181
msgid "{count, plural, one {Notebook created} other {# notebooks created}}"
msgstr ""
@@ -342,7 +347,7 @@ msgstr ""
msgid "{count, plural, one {Notebook deleted} other {# notebooks deleted}}"
msgstr ""
#: generated/actions.ts:168
#: generated/actions.ts:162
msgid "{count, plural, one {Notebook edited} other {# notebooks edited}}"
msgstr ""
@@ -354,7 +359,7 @@ msgstr ""
msgid "{count, plural, one {Notebook permanently deleted} other {# notebooks permanently deleted}}"
msgstr ""
#: generated/actions.ts:139
#: generated/actions.ts:133
msgid "{count, plural, one {Notebook restored} other {# notebooks restored}}"
msgstr ""
@@ -367,11 +372,6 @@ msgstr ""
msgid "{count, plural, one {Permanently delete item} other {Permanently delete # items}}"
msgstr ""
#: generated/in-progress-actions.ts:66
#: generated/in-progress-actions.ts:75
msgid "{count, plural, one {permanently deleting item...} other {permanently deleting # items...}}"
msgstr ""
#: generated/do-actions.ts:99
msgid "{count, plural, one {Pin item} other {Pin # items}}"
msgstr ""
@@ -444,16 +444,11 @@ msgstr ""
msgid "{count, plural, one {Restore notebook} other {Restore # notebooks}}"
msgstr ""
#: generated/in-progress-actions.ts:49
#: generated/in-progress-actions.ts:58
msgid "{count, plural, one {Restoring item...} other {Restoring # items...}}"
msgstr ""
#: generated/actions.ts:195
#: generated/actions.ts:189
msgid "{count, plural, one {Shortcut created} other {# shortcuts created}}"
msgstr ""
#: generated/actions.ts:191
#: generated/actions.ts:185
msgid "{count, plural, one {Tag created} other {# tags created}}"
msgstr ""
@@ -461,7 +456,7 @@ msgstr ""
msgid "{count, plural, one {Tag deleted} other {# tags deleted}}"
msgstr ""
#: generated/actions.ts:164
#: generated/actions.ts:158
msgid "{count, plural, one {Tag edited} other {# tags edited}}"
msgstr ""
@@ -618,7 +613,7 @@ msgid "A to Z"
msgstr ""
#: src/strings.ts:447
msgid "A vault stores your notes in an encrypted storage."
msgid "A vault stores your notes in a encrypted storage."
msgstr ""
#: src/strings.ts:661

View File

@@ -83,7 +83,7 @@ const ACTIONS = [
{
action: "permanentlyDeleted",
label: "permanently deleted",
dataTypes: ["note", "notebook", "item"]
dataTypes: ["note", "notebook"]
},
{ action: "published", label: "published", dataTypes: ["note"] },
{ action: "unpublished", label: "unpublished", dataTypes: ["note"] },
@@ -121,7 +121,7 @@ const ACTION_CONFIRMATIONS = [
"tag",
"color",
"attachment"
],
]
},
{
action: "permanentlyDelete",
@@ -148,16 +148,6 @@ const IN_PROGRESS_ACTIONS = [
action: "deleting",
label: "Deleting",
dataTypes: ["note", "notebook", "attachment", "tag", "reminder"]
},
{
action: "restoring",
label: "Restoring",
dataTypes: ["item"]
},
{
action: "permanentlyDeleting",
label: "permanently deleting",
dataTypes: ["item"]
}
];