mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 02:58:34 +02:00
Compare commits
4 Commits
3.0.24-and
...
fix-encryp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dda177a944 | ||
|
|
cc75f6588a | ||
|
|
ee8701eff8 | ||
|
|
fe3c5484c3 |
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -339,7 +339,7 @@ export async function hash(
|
||||
DatabaseLogger.log(`Hashing password: fallback: ${options?.usesFallback}`);
|
||||
|
||||
if (options?.usesFallback && Platform.OS !== "ios") {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -350,17 +350,7 @@ export async function hash(
|
||||
}
|
||||
|
||||
export async function generateCryptoKey(password: string, salt?: string) {
|
||||
return Sodium.deriveKey(password, salt) as Promise<SerializedKey>;
|
||||
}
|
||||
|
||||
export async function generateCryptoKeyFallback(
|
||||
password: string,
|
||||
salt?: string
|
||||
): Promise<SerializedKey> {
|
||||
return Sodium.deriveKeyFallback?.(
|
||||
password,
|
||||
salt as string
|
||||
) as Promise<SerializedKey>;
|
||||
return (await Sodium.deriveKey(password, salt)) as Promise<SerializedKey>;
|
||||
}
|
||||
|
||||
export function getAlgorithm(base64Variant: number) {
|
||||
|
||||
@@ -27,8 +27,7 @@ import {
|
||||
encryptMulti,
|
||||
generateCryptoKey,
|
||||
getCryptoKey,
|
||||
hash,
|
||||
generateCryptoKeyFallback
|
||||
hash
|
||||
} from "./encryption";
|
||||
import { MMKV } from "./mmkv";
|
||||
|
||||
@@ -147,6 +146,5 @@ export const Storage: IStorage = {
|
||||
decryptMulti,
|
||||
deriveCryptoKey,
|
||||
generateCryptoKey,
|
||||
generateCryptoKeyFallback,
|
||||
deriveCryptoKeyFallback
|
||||
};
|
||||
|
||||
@@ -24,12 +24,12 @@ import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { subscribe, zip } from "react-native-zip-archive";
|
||||
import filesystem from ".";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { presentSheet, ToastManager } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import filesystem from "../filesystem";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ export const ChangePassword = () => {
|
||||
throw new Error(strings.backupFailed() + `: ${result.error}`);
|
||||
}
|
||||
|
||||
await db.user.clearSessions();
|
||||
await db.user.changePassword(oldPassword.current, password.current);
|
||||
ToastManager.show({
|
||||
heading: strings.passwordChangedSuccessfully(),
|
||||
@@ -88,7 +89,6 @@ export const ChangePassword = () => {
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
} catch (e) {
|
||||
console.log(e.stack);
|
||||
setLoading(false);
|
||||
ToastManager.show({
|
||||
heading: strings.passwordChangeFailed(),
|
||||
|
||||
@@ -128,6 +128,7 @@ const Intro = ({ navigation }) => {
|
||||
style={{
|
||||
width: deviceMode !== "mobile" ? width / 2 : "100%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
@@ -137,7 +138,7 @@ const Intro = ({ navigation }) => {
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
paddingTop: insets.top + 10,
|
||||
paddingBottom: insets.top + 10,
|
||||
minHeight: height * 0.7 - (insets.top + insets.bottom)
|
||||
minHeight: height * 0.7
|
||||
}}
|
||||
>
|
||||
<SwiperFlatList
|
||||
|
||||
@@ -134,10 +134,8 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
key="code-input"
|
||||
fwdRef={codeInputRef}
|
||||
placeholder={strings.code()}
|
||||
defaultValue=""
|
||||
placeholder={strings.verifyNewEmail()}
|
||||
onChangeText={(code) => {
|
||||
emailChangeData.current.code = code;
|
||||
}}
|
||||
|
||||
@@ -17,15 +17,9 @@ 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 {
|
||||
EV,
|
||||
EVENTS,
|
||||
EventManagerSubscription,
|
||||
SYNC_CHECK_IDS,
|
||||
SyncStatusEvent,
|
||||
User
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { User } from "@notesnook/core";
|
||||
import { EV, EVENTS, SYNC_CHECK_IDS, SyncStatusEvent } from "@notesnook/core";
|
||||
import { EventManagerSubscription } from "@notesnook/core";
|
||||
import notifee from "@notifee/react-native";
|
||||
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
@@ -47,7 +41,6 @@ import * as RNIap from "react-native-iap";
|
||||
import { DatabaseLogger, db, setupDatabase } from "../common/database";
|
||||
import { initializeLogger } from "../common/database/logger";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
@@ -59,7 +52,6 @@ import {
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
import BiometricService from "../services/biometrics";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
@@ -74,17 +66,14 @@ import {
|
||||
setRecoveryKeyMessage,
|
||||
setUpdateAvailableMessage
|
||||
} from "../services/message";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
import PremiumService from "../services/premium";
|
||||
import SettingsService from "../services/settings";
|
||||
import Sync from "../services/sync";
|
||||
import { clearAllStores, initAfterSync } from "../stores";
|
||||
import { refreshAllStores } from "../stores/create-db-collection-store";
|
||||
import { initAfterSync } from "../stores";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { changeSystemBarColors } from "../stores/use-theme-store";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/colors";
|
||||
import { BETA } from "../utils/constants";
|
||||
@@ -100,8 +89,11 @@ import {
|
||||
} from "../utils/events";
|
||||
import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { changeSystemBarColors } from "../stores/use-theme-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -148,7 +140,6 @@ const onUploadedAttachmentProgress = (data: any) => {
|
||||
};
|
||||
|
||||
const onUserSessionExpired = async () => {
|
||||
console.log("LOGGED OUT USER....");
|
||||
SettingsService.set({
|
||||
sessionExpired: true
|
||||
});
|
||||
@@ -217,19 +208,11 @@ const onRequestPartialSync = async (
|
||||
};
|
||||
|
||||
const onLogout = async (reason: string) => {
|
||||
DatabaseLogger.log("User Logged Out " + reason);
|
||||
setLoginMessage();
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometricService.resetCredentials();
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setImmediate(() => {
|
||||
refreshAllStores();
|
||||
DatabaseLogger.log("User Logged Out" + reason);
|
||||
Notifications.setupReminders(true);
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
SettingsService.resetSettings();
|
||||
useUserStore.getState().setUser(null);
|
||||
useUserStore.getState().setSyncing(false);
|
||||
};
|
||||
|
||||
async function checkForShareExtensionLaunchedInBackground() {
|
||||
@@ -598,8 +581,7 @@ export const useAppEvents = () => {
|
||||
EV.subscribe(EVENTS.migrationStarted, (name) => {
|
||||
if (
|
||||
name !== "notesnook" ||
|
||||
!SettingsService.getProperty("introCompleted") ||
|
||||
Config.isTesting === "true"
|
||||
!SettingsService.getProperty("introCompleted")
|
||||
)
|
||||
return;
|
||||
startProgress({
|
||||
@@ -612,8 +594,7 @@ export const useAppEvents = () => {
|
||||
EV.subscribe(EVENTS.migrationFinished, (name) => {
|
||||
if (
|
||||
name !== "notesnook" ||
|
||||
!SettingsService.getProperty("introCompleted") ||
|
||||
Config.isTesting === "true"
|
||||
!SettingsService.getProperty("introCompleted")
|
||||
)
|
||||
return;
|
||||
endProgress();
|
||||
|
||||
@@ -97,7 +97,7 @@ const _TabsHolder = () => {
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
return;
|
||||
}
|
||||
if (item && item.type === "notesnook.action.newnote") {
|
||||
if (item.type === "notesnook.action.newnote") {
|
||||
clearAppState();
|
||||
if (!tabBarRef.current) {
|
||||
await sleep(3000);
|
||||
|
||||
@@ -91,6 +91,7 @@ export const Reminders = ({
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
title={strings.setReminder()}
|
||||
onPress={() => {
|
||||
ReminderSheet.present();
|
||||
}}
|
||||
|
||||
@@ -17,9 +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 { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, {
|
||||
Dispatch,
|
||||
@@ -29,11 +26,11 @@ import React, {
|
||||
useState
|
||||
} from "react";
|
||||
import { ActivityIndicator, Linking, Platform, View } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import Storage from "../../common/database/storage";
|
||||
import DialogHeader from "../../components/dialog/dialog-header";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
@@ -49,10 +46,13 @@ import {
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
const mfaMethods: MFAMethod[] = [
|
||||
{
|
||||
id: "app",
|
||||
@@ -525,7 +525,7 @@ export const MFARecoveryCodes = ({
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
path = await Storage.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
path + fileName,
|
||||
codeString,
|
||||
|
||||
@@ -45,7 +45,7 @@ const Home = ({
|
||||
});
|
||||
|
||||
const renderItem = ({ item }: { item: SettingSection; index: number }) =>
|
||||
item.id === "account" ? (
|
||||
item.name === "account" ? (
|
||||
<SettingsUserSection item={item} />
|
||||
) : (
|
||||
<SectionGroup item={item} />
|
||||
|
||||
@@ -18,10 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import { User } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee from "@notifee/react-native";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Appearance, Linking, Platform } from "react-native";
|
||||
@@ -30,6 +28,7 @@ import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import ScreenGuardModule from "react-native-screenguard";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
@@ -55,11 +54,14 @@ import {
|
||||
openVault,
|
||||
presentSheet
|
||||
} from "../../services/event-manager";
|
||||
import { setLoginMessage } from "../../services/message";
|
||||
import Navigation from "../../services/navigation";
|
||||
import Notifications from "../../services/notifications";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import Sync from "../../services/sync";
|
||||
import { clearAllStores } from "../../stores";
|
||||
import { refreshAllStores } from "../../stores/create-db-collection-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
@@ -75,6 +77,9 @@ import { useDragState } from "./editor/state";
|
||||
import { verifyUser, verifyUserWithApplock } from "./functions";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
|
||||
type User = any;
|
||||
|
||||
export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
@@ -114,10 +119,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
"MMMM D, YYYY"
|
||||
);
|
||||
|
||||
if (user.subscription.provider === 4) {
|
||||
return strings.subEndsOn(expiryDate);
|
||||
}
|
||||
|
||||
return user.subscription?.type === 2
|
||||
? strings.signedUpOn(startDate)
|
||||
: user.subscription?.type === 1
|
||||
@@ -133,39 +134,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
: strings.neverHesitate();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "redeem-gift-code",
|
||||
name: strings.redeemGiftCode(),
|
||||
description: strings.redeemGiftCodeDesc(),
|
||||
hidden: (current) => {
|
||||
return !current as boolean;
|
||||
},
|
||||
useHook: () =>
|
||||
useUserStore(
|
||||
(state) =>
|
||||
state.user?.subscription.type == SUBSCRIPTION_STATUS.TRIAL ||
|
||||
state.user?.subscription.type == SUBSCRIPTION_STATUS.BASIC
|
||||
),
|
||||
icon: "gift",
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
title: strings.redeemGiftCode(),
|
||||
paragraph: strings.redeemGiftCodeDesc(),
|
||||
input: true,
|
||||
inputPlaceholder: strings.code(),
|
||||
positiveText: strings.redeem(),
|
||||
positivePress: async (value) => {
|
||||
db.subscriptions.redeemCode(value).catch((e) => {
|
||||
ToastManager.show({
|
||||
heading: "Error redeeming code",
|
||||
message: (e as Error).message,
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "account-settings",
|
||||
type: "screen",
|
||||
@@ -479,6 +447,18 @@ export const settingsGroups: SettingSection[] = [
|
||||
});
|
||||
|
||||
await db.user?.logout();
|
||||
setLoginMessage();
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometricService.resetCredentials();
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setImmediate(() => {
|
||||
refreshAllStores();
|
||||
});
|
||||
Navigation.queueRoutesForUpdate();
|
||||
SettingsService.resetSettings();
|
||||
useUserStore.getState().setUser(null);
|
||||
useUserStore.getState().setSyncing(false);
|
||||
endProgress();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
@@ -1194,12 +1174,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "select-backup-dir",
|
||||
name: strings.selectBackupDir(),
|
||||
description: () => {
|
||||
const desc = strings.selectBackupDirDesc(
|
||||
SettingsService.get().backupDirectoryAndroid?.path || ""
|
||||
);
|
||||
return desc[0] + " " + desc[1];
|
||||
},
|
||||
description: strings.selectBackupDirDesc(),
|
||||
icon: "folder",
|
||||
hidden: () =>
|
||||
!!SettingsService.get().backupDirectoryAndroid ||
|
||||
|
||||
@@ -126,19 +126,18 @@ export const Subscription = () => {
|
||||
user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM_EXPIRED &&
|
||||
user.subscription?.type !== SUBSCRIPTION_STATUS.BASIC ? (
|
||||
<Button
|
||||
title={subscriptionProviderInfo?.title()}
|
||||
title={subscriptionProviderInfo?.title}
|
||||
onPress={() => {
|
||||
presentSheet({
|
||||
title: subscriptionProviderInfo.title(),
|
||||
paragraph: subscriptionProviderInfo.desc()
|
||||
title: subscriptionProviderInfo.title,
|
||||
paragraph: subscriptionProviderInfo.desc
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
width: "100%",
|
||||
paddingHorizontal: 0
|
||||
borderRadius: 100
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
fontSize={SIZE.sm}
|
||||
height={30}
|
||||
type="secondaryAccented"
|
||||
/>
|
||||
|
||||
@@ -85,13 +85,9 @@ async function checkBackupDirExists(reset = false, context = "global") {
|
||||
resolve(await getDirectoryAndroid());
|
||||
return;
|
||||
}
|
||||
const desc = strings.selectBackupDirDesc(
|
||||
SettingsService.get().backupDirectoryAndroid?.path || ""
|
||||
);
|
||||
|
||||
presentDialog({
|
||||
title: strings.selectBackupDir(),
|
||||
paragraph: desc[0] + " " + desc,
|
||||
paragraph: strings.selectBackupDirDesc(),
|
||||
positivePress: async () => {
|
||||
resolve(await getDirectoryAndroid());
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ import FingerprintScanner, {
|
||||
} from "@ammarahmed/react-native-fingerprint-scanner";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { Storage } from "../common/database/storage";
|
||||
import Storage from "../common/database/storage";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { ToastOptions, ToastManager } from "./event-manager";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
|
||||
@@ -116,7 +116,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3034
|
||||
versionCode 3033
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -129,16 +129,6 @@
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
- You can now share multiple files to Notesnook
|
||||
- Fix file and image sharing not working
|
||||
- Many other bug fixes and small improvements
|
||||
- Added full support for localization in Notesnook
|
||||
- Improved search experience
|
||||
- Allow user to cancel logging in
|
||||
- Fixed scrolling focused line into view
|
||||
- Support self hosted monograph server
|
||||
- Fixed markdown link pasting in editor
|
||||
- Many other bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
Binary file not shown.
@@ -5,11 +5,11 @@
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>org.streetwriters.notesnook</key>
|
||||
<string>Notesnook App Distribution 2025</string>
|
||||
<string>Notesnook App Distribution 2024</string>
|
||||
<key>org.streetwriters.notesnook.notewidget</key>
|
||||
<string>Notesnook Widget Distribution 2025</string>
|
||||
<string>Notesnook Widget Distribution 2024</string>
|
||||
<key>org.streetwriters.notesnook.share</key>
|
||||
<string>Notesnook Share Distribution 2025</string>
|
||||
<string>Notesnook Share Distribution 2024</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1063,7 +1063,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1137,7 +1137,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1168,7 +1168,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1252,7 +1252,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2024";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1401,7 +1401,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1413,7 +1413,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1444,7 +1444,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1457,12 +1457,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -1487,7 +1487,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1561,7 +1561,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1592,7 +1592,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2121;
|
||||
CURRENT_PROJECT_VERSION = 2120;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1667,12 +1667,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.24;
|
||||
MARKETING_VERSION = 3.0.23;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1744,7 +1744,10 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
@@ -1808,7 +1811,10 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
|
||||
@@ -1019,7 +1019,7 @@ PODS:
|
||||
- react-native-screenguard (1.0.0):
|
||||
- React-Core
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- react-native-share-extension (2.7.0):
|
||||
- react-native-share-extension (2.6.0):
|
||||
- React
|
||||
- react-native-sodium (1.6.1):
|
||||
- React
|
||||
@@ -1858,7 +1858,7 @@ SPEC CHECKSUMS:
|
||||
react-native-quick-sqlite: 18e1367c34faac90e37f6eb3e78c196e9b674b5d
|
||||
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
|
||||
react-native-screenguard: 8b36a3df84c76cd2b82c477f71c26fa1c8cc14a0
|
||||
react-native-share-extension: 17e42444d0d9fbfeb0a7392899def70f9534c9c4
|
||||
react-native-share-extension: 25437eb1039f7409be6e80a7edf8d02b42e1dc99
|
||||
react-native-sodium: 4cb76086943a7f60c42b40ebca866695b360a196
|
||||
react-native-theme-switch-animation: d3eb50365a3829ce5572628888fa514752703f61
|
||||
react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0"
|
||||
"@ammarahmed/react-native-share-extension": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"detox": "^20.27.6",
|
||||
|
||||
158
apps/mobile/package-lock.json
generated
158
apps/mobile/package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"app/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ammarahmed/react-native-share-extension": "^2.7.0",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -28931,8 +28932,7 @@
|
||||
"@ammarahmed/react-native-background-fetch": "^4.2.2",
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.1",
|
||||
"@ammarahmed/react-native-sodium": "1.5.6",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^4.1.1",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
@@ -29070,9 +29070,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-share-extension": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.8.0.tgz",
|
||||
"integrity": "sha512-eDFVKiJJxjnIORveMUKVJGIhKfRNjqJWIoKS+JM88q8I8e3jHys9moyJAr9QM5oW/tZx4yyItl08yRXGnr7Grg==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.7.0.tgz",
|
||||
"integrity": "sha512-M7mNKv8k+j3SkD0NcRAOItoF7lMa61OYnTD7JOAZiBRHWkZDysc/2u6dbQGDcd6VMRif+xWRrhLILH+zFHkgPg==",
|
||||
"dependencies": {
|
||||
"react-native": "^0.63.1"
|
||||
}
|
||||
@@ -35720,14 +35720,15 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.2"
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -35736,18 +35737,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
@@ -36784,19 +36773,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"dev": true,
|
||||
@@ -36911,6 +36887,29 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
@@ -37016,9 +37015,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -37036,17 +37038,6 @@
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
@@ -38228,20 +38219,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
||||
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"dunder-proto": "^1.0.0",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.0.0"
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -38395,11 +38381,10 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -38490,7 +38475,6 @@
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -38500,9 +38484,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -41262,14 +41245,6 @@
|
||||
"version": "1.2.5",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"license": "CC0-1.0"
|
||||
@@ -43145,28 +43120,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-freeze": {
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
@@ -44320,6 +44273,13 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/sanitize-filename": {
|
||||
"version": "1.6.3",
|
||||
"dev": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.24",
|
||||
"version": "3.0.23",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -52,4 +52,4 @@
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.36.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,7 @@ const EXTRA_ICON_NAMES = [
|
||||
"notebook-plus",
|
||||
"arrow-right-bold-box-outline",
|
||||
"arrow-up-bold",
|
||||
"login",
|
||||
"gift"
|
||||
"login"
|
||||
];
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
@@ -43,7 +43,7 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import isURL from "validator/lib/isURL";
|
||||
import { DatabaseLogger, db } from "../app/common/database";
|
||||
import { Storage } from "../app/common/database/storage";
|
||||
import Storage from "../app/common/database/storage";
|
||||
import { Button } from "../app/components/ui/button";
|
||||
import Heading from "../app/components/ui/typography/heading";
|
||||
import Paragraph from "../app/components/ui/typography/paragraph";
|
||||
@@ -620,45 +620,41 @@ const ShareView = () => {
|
||||
>
|
||||
Tap to remove an attachment.
|
||||
</Paragraph>
|
||||
{rawFiles.some((item) => isImage(item.type)) ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 6
|
||||
}}
|
||||
onPress={() => {
|
||||
setCompress(!compress);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={20}
|
||||
name={
|
||||
compress
|
||||
? "checkbox-marked"
|
||||
: "checkbox-blank-outline"
|
||||
}
|
||||
color={
|
||||
compress ? colors.primary.accent : colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 6
|
||||
}}
|
||||
onPress={() => {
|
||||
setCompress(!compress);
|
||||
flexShrink: 1,
|
||||
marginLeft: 3,
|
||||
fontSize: 12
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={20}
|
||||
name={
|
||||
compress
|
||||
? "checkbox-marked"
|
||||
: "checkbox-blank-outline"
|
||||
}
|
||||
color={
|
||||
compress
|
||||
? colors.primary.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
marginLeft: 3,
|
||||
fontSize: 12
|
||||
}}
|
||||
>
|
||||
Compress image(s) (recommended)
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
Compress image (recommended)
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : null}
|
||||
<View
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: ₹400
|
||||
Sales tax: ₹300
|
||||
Discount: -₹400
|
||||
Total: ₹400
|
||||
Total: ₹400/yr
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: ₹400
|
||||
Sales tax: ₹300
|
||||
Discount: -₹400
|
||||
Total: ₹400
|
||||
Total: ₹400/yr
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: ₹400
|
||||
Sales tax: ₹300
|
||||
Discount: -₹400
|
||||
Total: ₹400
|
||||
Total: ₹400/yr
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: $200
|
||||
Sales tax: $0
|
||||
Discount: -$200
|
||||
Total: $200
|
||||
Total: $200/yr
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: $200
|
||||
Sales tax: $0
|
||||
Discount: -$200
|
||||
Total: $200
|
||||
Total: $200/yr
|
||||
@@ -1,4 +1,4 @@
|
||||
Subtotal: $200
|
||||
Sales tax: $0
|
||||
Discount: -$200
|
||||
Total: $200
|
||||
Total: $200/yr
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { getTestId, NOTE, TITLE_ONLY_NOTE } from "./utils";
|
||||
import { NOTE, TITLE_ONLY_NOTE } from "./utils";
|
||||
|
||||
test("focus mode", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
@@ -270,103 +270,3 @@ test("#1468 count words separated by newlines", async ({ page }) => {
|
||||
|
||||
expect((await notes.editor.getWordCount()) === 10).toBeTruthy();
|
||||
});
|
||||
|
||||
test("disable autosave when note crosses MAX_AUTO_SAVEABLE_WORDS", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const content = "a ".repeat(100);
|
||||
|
||||
await notes.createNote({
|
||||
title: "many words",
|
||||
content
|
||||
});
|
||||
|
||||
expect(
|
||||
await app.toasts.waitForToast(
|
||||
"Auto-save is disabled for large notes. Press Ctrl + S to save."
|
||||
)
|
||||
).toBe(true);
|
||||
await expect(notes.editor.notSavedIcon).toBeVisible();
|
||||
});
|
||||
|
||||
test("when autosave is disabled, pressing ctrl+s should save the note", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const content = "a ".repeat(100);
|
||||
await notes.createNote({
|
||||
title: NOTE.title,
|
||||
content
|
||||
});
|
||||
|
||||
await page.keyboard.press("Control+s");
|
||||
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
});
|
||||
|
||||
test("when autosave is disabled, switching to another note should save the note", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const content = "a ".repeat(100);
|
||||
const note1 = await notes.createNote({
|
||||
title: "Test note 1"
|
||||
});
|
||||
const note2 = await notes.createNote({
|
||||
title: "Test note 2"
|
||||
});
|
||||
await note1?.openNote();
|
||||
await notes.editor.setContent(content);
|
||||
|
||||
await note2?.openNote();
|
||||
|
||||
await note1?.openNote();
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
expect(await notes.editor.getContent("text")).toBe(content.trim());
|
||||
});
|
||||
|
||||
test("when autosave is disabled, creating a new note should save the note", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const content = "a ".repeat(100);
|
||||
const note = await notes.createNote({
|
||||
title: NOTE.title,
|
||||
content
|
||||
});
|
||||
|
||||
await notes.newNote();
|
||||
|
||||
await note?.openNote();
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
expect(await notes.editor.getContent("text")).toBe(content.trim());
|
||||
});
|
||||
|
||||
test("when autosave is disabled, closing the note should save it", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const content = "a ".repeat(100);
|
||||
const note = await notes.createNote({
|
||||
title: "Title",
|
||||
content
|
||||
});
|
||||
|
||||
const noteTab = await notes.editor.findTab((await note!.getId())!);
|
||||
await noteTab?.close();
|
||||
|
||||
await note?.openNote();
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
expect(await notes.editor.getContent("text")).toBe(content.trim());
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export class BaseItemModel {
|
||||
}
|
||||
|
||||
async getId() {
|
||||
return (await this.locator.getAttribute("id"))?.replace("id_", "");
|
||||
return await this.locator.getAttribute("id");
|
||||
}
|
||||
|
||||
async getTitle() {
|
||||
|
||||
@@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { TabItemModel } from "./tab-item.model";
|
||||
import { iterateList } from "./utils";
|
||||
|
||||
export class EditorModel {
|
||||
private readonly page: Page;
|
||||
@@ -35,9 +33,6 @@ export class EditorModel {
|
||||
private readonly wordCountText: Locator;
|
||||
private readonly dateEditedText: Locator;
|
||||
private readonly searchButton: Locator;
|
||||
private readonly tabsList: Locator;
|
||||
readonly savedIcon: Locator;
|
||||
readonly notSavedIcon: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -56,9 +51,6 @@ export class EditorModel {
|
||||
this.wordCountText = page.locator(getTestId("editor-word-count"));
|
||||
this.dateEditedText = page.locator(getTestId("editor-date-edited"));
|
||||
this.searchButton = page.locator(getTestId("Search"));
|
||||
this.savedIcon = page.locator(getTestId("editor-save-state-saved"));
|
||||
this.notSavedIcon = page.locator(getTestId("editor-save-state-notsaved"));
|
||||
this.tabsList = page.locator(getTestId("tabs"));
|
||||
}
|
||||
|
||||
async waitForLoading(title?: string, content?: string) {
|
||||
@@ -235,11 +227,4 @@ export class EditorModel {
|
||||
.replace(" words", "")
|
||||
);
|
||||
}
|
||||
|
||||
async findTab(id: string) {
|
||||
for await (const item of iterateList(this.tabsList.locator(".tab"))) {
|
||||
const tabModel = new TabItemModel(item, this.page);
|
||||
if ((await tabModel.getId()) === id) return tabModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
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 { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
|
||||
export class TabItemModel {
|
||||
private readonly closeButton: Locator;
|
||||
constructor(private readonly locator: Locator, page: Page) {
|
||||
this.closeButton = locator.locator(getTestId("tab-close-button"));
|
||||
}
|
||||
|
||||
async getId() {
|
||||
const testId = await this.locator.getAttribute("data-test-id");
|
||||
return testId?.replace("tab-", "");
|
||||
}
|
||||
close() {
|
||||
return this.closeButton.click();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import {
|
||||
getTestId,
|
||||
groupByOptions,
|
||||
NOTE,
|
||||
orderByOptions,
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.23",
|
||||
"version": "3.0.22",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -206,8 +206,7 @@ function AuthContainer(props) {
|
||||
>
|
||||
<Text variant={"subBody"}>
|
||||
{version.status === "fulfilled" &&
|
||||
!!version.value &&
|
||||
version.value.instance !== "default" ? (
|
||||
version.value?.instance !== "default" ? (
|
||||
<>
|
||||
{strings.usingInstance(
|
||||
version.value.instance,
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
Lock,
|
||||
NormalMode,
|
||||
Note,
|
||||
NoteRemove,
|
||||
Pin,
|
||||
Properties,
|
||||
Publish,
|
||||
@@ -45,7 +44,6 @@ import {
|
||||
} from "../icons";
|
||||
import { ScrollContainer } from "@notesnook/ui";
|
||||
import {
|
||||
SaveState,
|
||||
SessionType,
|
||||
isLockedSession,
|
||||
useEditorStore
|
||||
@@ -262,7 +260,7 @@ function TabStrip() {
|
||||
style={{ flex: 1 }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
"--ms-track-size": "6px"
|
||||
pointerEvents: "none"
|
||||
})}
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
onWheel={(e) => {
|
||||
@@ -283,7 +281,6 @@ function TabStrip() {
|
||||
e.stopPropagation();
|
||||
useEditorStore.getState().newSession();
|
||||
}}
|
||||
data-test-id="tabs"
|
||||
>
|
||||
<ReorderableList
|
||||
items={sessions}
|
||||
@@ -305,95 +302,89 @@ function TabStrip() {
|
||||
sessions.splice(to, 0, fromTab);
|
||||
useEditorStore.setState({ sessions });
|
||||
}}
|
||||
renderItem={({ item: session, index: i }) => {
|
||||
const isUnsaved =
|
||||
session.type === "default" &&
|
||||
session.saveState === SaveState.NotSaved;
|
||||
return (
|
||||
<Tab
|
||||
id={session.id}
|
||||
key={session.id}
|
||||
title={
|
||||
session.title ||
|
||||
("note" in session ? session.note.title : "Untitled")
|
||||
}
|
||||
isUnsaved={isUnsaved}
|
||||
isTemporary={!!session.preview}
|
||||
isActive={session.id === activeSessionId}
|
||||
isPinned={!!session.pinned}
|
||||
isLocked={isLockedSession(session)}
|
||||
type={session.type}
|
||||
onKeepOpen={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.updateSession(
|
||||
session.id,
|
||||
[session.type],
|
||||
(s) => (s.preview = false)
|
||||
)
|
||||
}
|
||||
onFocus={() => {
|
||||
if (session.id !== activeSessionId) {
|
||||
useEditorStore.getState().openSession(session.id);
|
||||
}
|
||||
}}
|
||||
onClose={() =>
|
||||
useEditorStore.getState().closeSessions(session.id)
|
||||
}
|
||||
onCloseAll={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions.filter((s) => !s.pinned).map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseOthers={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s) => s.id !== session.id && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheRight={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index > i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheLeft={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index < i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onRevealInList={() =>
|
||||
AppEventManager.publish(
|
||||
AppEvents.revealItemInList,
|
||||
"note" in session ? session.note.id : session.id,
|
||||
true
|
||||
renderItem={({ item: session, index: i }) => (
|
||||
<Tab
|
||||
id={session.id}
|
||||
key={session.id}
|
||||
title={
|
||||
session.title ||
|
||||
("note" in session ? session.note.title : "Untitled")
|
||||
}
|
||||
isTemporary={!!session.preview}
|
||||
isActive={session.id === activeSessionId}
|
||||
isPinned={!!session.pinned}
|
||||
isLocked={isLockedSession(session)}
|
||||
type={session.type}
|
||||
onKeepOpen={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.updateSession(
|
||||
session.id,
|
||||
[session.type],
|
||||
(s) => (s.preview = false)
|
||||
)
|
||||
}
|
||||
onFocus={() => {
|
||||
if (session.id !== activeSessionId) {
|
||||
useEditorStore.getState().openSession(session.id);
|
||||
}
|
||||
onPin={() => {
|
||||
useEditorStore.setState((state) => {
|
||||
// preview tabs can never be pinned.
|
||||
if (!session.pinned) state.sessions[i].preview = false;
|
||||
state.sessions[i].pinned = !session.pinned;
|
||||
state.sessions.sort((a, b) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
}}
|
||||
onClose={() =>
|
||||
useEditorStore.getState().closeSessions(session.id)
|
||||
}
|
||||
onCloseAll={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions.filter((s) => !s.pinned).map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseOthers={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s) => s.id !== session.id && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheRight={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index > i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheLeft={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index < i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onRevealInList={() =>
|
||||
AppEventManager.publish(
|
||||
AppEvents.revealItemInList,
|
||||
"note" in session ? session.note.id : session.id,
|
||||
true
|
||||
)
|
||||
}
|
||||
onPin={() => {
|
||||
useEditorStore.setState((state) => {
|
||||
// preview tabs can never be pinned.
|
||||
if (!session.pinned) state.sessions[i].preview = false;
|
||||
state.sessions[i].pinned = !session.pinned;
|
||||
state.sessions.sort((a, b) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Flex>
|
||||
</ScrollContainer>
|
||||
@@ -407,7 +398,6 @@ type TabProps = {
|
||||
isTemporary: boolean;
|
||||
isPinned: boolean;
|
||||
isLocked: boolean;
|
||||
isUnsaved: boolean;
|
||||
type: SessionType;
|
||||
onKeepOpen: () => void;
|
||||
onFocus: () => void;
|
||||
@@ -427,7 +417,6 @@ function Tab(props: TabProps) {
|
||||
isTemporary,
|
||||
isPinned,
|
||||
isLocked,
|
||||
isUnsaved,
|
||||
type,
|
||||
onKeepOpen,
|
||||
onFocus,
|
||||
@@ -447,8 +436,6 @@ function Tab(props: TabProps) {
|
||||
? Readonly
|
||||
: type === "deleted"
|
||||
? Trash
|
||||
: isUnsaved
|
||||
? NoteRemove
|
||||
: Note;
|
||||
const { attributes, listeners, setNodeRef, transform, transition, active } =
|
||||
useSortable({ id });
|
||||
@@ -457,7 +444,6 @@ function Tab(props: TabProps) {
|
||||
<Flex
|
||||
ref={setNodeRef}
|
||||
className="tab"
|
||||
data-test-id={`tab-${id}`}
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
cursor: "pointer",
|
||||
@@ -558,13 +544,7 @@ function Tab(props: TabProps) {
|
||||
if (e.button == 0) onFocus();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
data-test-id={`tab-icon${isUnsaved ? "-unsaved" : ""}`}
|
||||
size={16}
|
||||
color={
|
||||
isUnsaved ? "accent-error" : isActive ? "accent-selected" : "icon"
|
||||
}
|
||||
/>
|
||||
<Icon size={16} color={isActive ? "accent-selected" : "icon"} />
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
@@ -610,7 +590,6 @@ function Tab(props: TabProps) {
|
||||
}
|
||||
}}
|
||||
className="closeTabButton"
|
||||
data-test-id={"tab-close-button"}
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -121,13 +121,6 @@ function EditorFooter() {
|
||||
) : null}
|
||||
{SaveStateIcon && (
|
||||
<SaveStateIcon
|
||||
data-test-id={`editor-save-state-${
|
||||
saveState === SaveState.Saved
|
||||
? "saved"
|
||||
: saveState === SaveState.NotSaved
|
||||
? "notsaved"
|
||||
: "loading"
|
||||
}`}
|
||||
size={13}
|
||||
color={
|
||||
saveState === SaveState.Saved
|
||||
|
||||
@@ -71,12 +71,9 @@ import { logger } from "../../utils/logger";
|
||||
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
|
||||
import { NoteLinkingDialog } from "../../dialogs/note-linking-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { onPageVisibilityChanged } from "../../utils/page-visibility";
|
||||
|
||||
const PDFPreview = React.lazy(() => import("../pdf-preview"));
|
||||
|
||||
const autoSaveToast = { show: true, hide: () => {} };
|
||||
|
||||
async function saveContent(
|
||||
noteId: string,
|
||||
ignoreEdit: boolean,
|
||||
@@ -442,17 +439,9 @@ export function Editor(props: EditorProps) {
|
||||
readonly: false,
|
||||
focusMode: false
|
||||
};
|
||||
const saveSessionContentIfNotSaved = useEditorStore(
|
||||
(store) => store.saveSessionContentIfNotSaved
|
||||
);
|
||||
const setEditorSaveState = useEditorStore((store) => store.setSaveState);
|
||||
useScrollToBlock(session);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoSaveToast.show) {
|
||||
autoSaveToast.hide();
|
||||
}
|
||||
|
||||
const event = AppEventManager.subscribe(
|
||||
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
|
||||
({ hash, loaded, total }: AttachmentProgress) => {
|
||||
@@ -469,15 +458,6 @@ export function Editor(props: EditorProps) {
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsub = onPageVisibilityChanged((_, hidden) => {
|
||||
if (hidden) {
|
||||
saveSessionContentIfNotSaved(id);
|
||||
}
|
||||
});
|
||||
return () => unsub();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EditorChrome {...props}>
|
||||
<Tiptap
|
||||
@@ -579,25 +559,6 @@ export function Editor(props: EditorProps) {
|
||||
const link = await NoteLinkingDialog.show({ attributes });
|
||||
return link || undefined;
|
||||
}}
|
||||
onAutoSaveDisabled={() => {
|
||||
setEditorSaveState(id, SaveState.NotSaved);
|
||||
if (autoSaveToast.show === false) return;
|
||||
const { hide } = showToast(
|
||||
"error",
|
||||
"Auto-save is disabled for large notes. Press Ctrl + S to save.",
|
||||
[
|
||||
{
|
||||
text: "Dismiss",
|
||||
onClick: () => {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
],
|
||||
Infinity
|
||||
);
|
||||
autoSaveToast.show = false;
|
||||
autoSaveToast.hide = hide;
|
||||
}}
|
||||
>
|
||||
{headless ? null : (
|
||||
<>
|
||||
|
||||
@@ -63,7 +63,6 @@ import useTablet from "../../hooks/use-tablet";
|
||||
import { TimeFormat } from "@notesnook/core";
|
||||
import { BuyDialog } from "../../dialogs/buy-dialog";
|
||||
import { EDITOR_ZOOM } from "./common";
|
||||
import { ScrollContainer } from "@notesnook/ui";
|
||||
|
||||
export type OnChangeHandler = (
|
||||
content: () => string,
|
||||
@@ -90,7 +89,6 @@ type TipTapProps = {
|
||||
) => Promise<LinkAttributes | undefined>;
|
||||
onAttachFile?: (file: File) => void;
|
||||
onFocus?: () => void;
|
||||
onAutoSaveDisabled: () => void;
|
||||
content?: () => string | undefined;
|
||||
readonly?: boolean;
|
||||
nonce?: number;
|
||||
@@ -134,11 +132,12 @@ function TipTap(props: TipTapProps) {
|
||||
onInsertInternalLink,
|
||||
onContentChange,
|
||||
onFocus = () => {},
|
||||
onAutoSaveDisabled,
|
||||
content,
|
||||
editorContainer,
|
||||
readonly,
|
||||
nonce,
|
||||
isMobile,
|
||||
isTablet,
|
||||
downloadOptions,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@@ -348,9 +347,6 @@ function TipTap(props: TipTapProps) {
|
||||
(s) => s.editors[id]?.statistics?.words.total,
|
||||
(totalWords) => {
|
||||
autoSave.current = !totalWords || totalWords < MAX_AUTO_SAVEABLE_WORDS;
|
||||
if (!autoSave.current) {
|
||||
onAutoSaveDisabled();
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
@@ -370,36 +366,14 @@ function TipTap(props: TipTapProps) {
|
||||
zIndex: 2
|
||||
}}
|
||||
>
|
||||
<ScrollContainer
|
||||
className="toolbarScroll"
|
||||
suppressScrollY
|
||||
style={{ display: "flex" }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
"--ms-track-size": "6px"
|
||||
})}
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
onWheel={(e) => {
|
||||
const scrollcontainer = document.querySelector(
|
||||
".active .toolbarScroll"
|
||||
);
|
||||
if (!scrollcontainer) return;
|
||||
if (e.deltaY > 0) scrollcontainer.scrollLeft += 100;
|
||||
else if (e.deltaY < 0) scrollcontainer.scrollLeft -= 100;
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
editor={editor}
|
||||
location={"top"}
|
||||
sx={{
|
||||
flexWrap: "unset",
|
||||
overflowX: "unset"
|
||||
}}
|
||||
tools={toolbarConfig}
|
||||
defaultFontFamily={fontFamily}
|
||||
defaultFontSize={fontSize}
|
||||
/>
|
||||
</ScrollContainer>
|
||||
<Toolbar
|
||||
editor={editor}
|
||||
location={"top"}
|
||||
sx={isTablet || isMobile ? { flexWrap: "nowrap" } : {}}
|
||||
tools={toolbarConfig}
|
||||
defaultFontFamily={fontFamily}
|
||||
defaultFontSize={fontSize}
|
||||
/>
|
||||
</ScopedThemeProvider>
|
||||
</>
|
||||
);
|
||||
@@ -602,9 +576,7 @@ function toIEditor(editor: Editor): IEditor {
|
||||
},
|
||||
{ query: (a) => a.hash === hash, preventUpdate: true }
|
||||
),
|
||||
startSearch: () => editor.commands.startSearch(),
|
||||
getContent: () =>
|
||||
getHTMLFromFragment(editor.state.doc.content, editor.schema)
|
||||
startSearch: () => editor.commands.startSearch()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Attachment } from "@notesnook/editor";
|
||||
|
||||
export const MAX_AUTO_SAVEABLE_WORDS = IS_TESTING ? 100 : 100_000;
|
||||
export const MAX_AUTO_SAVEABLE_WORDS = 100_000;
|
||||
|
||||
export type NoteStatistics = {
|
||||
words: {
|
||||
@@ -39,5 +39,4 @@ export interface IEditor {
|
||||
attachFile: (file: Attachment) => void;
|
||||
sendAttachmentProgress: (hash: string, progress: number) => void;
|
||||
startSearch: () => void;
|
||||
getContent: () => string;
|
||||
}
|
||||
|
||||
@@ -33,11 +33,7 @@ import { db } from "../../common/db";
|
||||
import { createDialect } from "../../common/sqlite";
|
||||
import { getDeviceInfo } from "../../utils/platform";
|
||||
|
||||
const IGNORED_ERRORS = [
|
||||
"Error in input stream",
|
||||
"network error",
|
||||
"NetworkError when attempting to fetch resource."
|
||||
];
|
||||
const IGNORED_ERRORS = ["Error in input stream", "network error"];
|
||||
export function GlobalErrorHandler(props: PropsWithChildren) {
|
||||
const { showBoundary } = useErrorBoundary();
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ const groupByMenu: (options: GroupingMenuOptions) => MenuItem | null = (
|
||||
icon: GroupBy.path,
|
||||
menu: {
|
||||
items: map(options, [
|
||||
{ key: "none", title: strings.groupByStrings.none() },
|
||||
{ key: "none", title: strings.groupByStrings.None() },
|
||||
{ key: "default", title: strings.groupByStrings.default() },
|
||||
{ key: "year", title: strings.groupByStrings.year() },
|
||||
{ key: "month", title: strings.groupByStrings.month() },
|
||||
|
||||
@@ -218,8 +218,7 @@ import {
|
||||
mdiServerSecurity,
|
||||
mdiOpenInNew,
|
||||
mdiTagOutline,
|
||||
mdiChatQuestionOutline,
|
||||
mdiNoteRemoveOutline
|
||||
mdiChatQuestionOutline
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -314,7 +313,6 @@ function createIcon(path: string, rotate = false) {
|
||||
|
||||
export const Plus = createIcon(mdiPlus);
|
||||
export const Note = createIcon(mdiNoteOutline);
|
||||
export const NoteRemove = createIcon(mdiNoteRemoveOutline);
|
||||
export const Notes = createIcon(mdiNoteMultipleOutline);
|
||||
export const Minus = createIcon(mdiMinus);
|
||||
export const Notebook = createIcon(mdiBookOutline);
|
||||
|
||||
@@ -147,6 +147,7 @@ function getDate(item: Item, groupType?: GroupingKey): number {
|
||||
groupType
|
||||
? db.settings.getGroupOptions(groupType)
|
||||
: {
|
||||
groupBy: "default",
|
||||
sortBy: "dateEdited",
|
||||
sortDirection: "desc"
|
||||
},
|
||||
|
||||
@@ -93,14 +93,29 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Notesnook Gift Cards",
|
||||
title: "5x faster startup",
|
||||
subtitle:
|
||||
"You can now gift Notesnook Pro to your friends and family. Gift cards are available in 1, 3, and 5-year plans at https://notesnook.com/giftcards."
|
||||
"We have optimized the app startup time by 5x (3000ms to 600ms). This means you can now start taking notes faster than ever."
|
||||
},
|
||||
{
|
||||
title: "Sorted search results",
|
||||
title: "Improved ToC UX",
|
||||
subtitle:
|
||||
"Search results are now sorted by date created by default."
|
||||
"We have made a bunch of improvements to the ToC (Table of Contents): it no longer covers the note content, and its open/closed state is preserved between app restarts."
|
||||
},
|
||||
{
|
||||
title: "Faster checkout",
|
||||
subtitle:
|
||||
"Checkout is now 1-step. You can now directly enter your payment details and complete the purchase without any intermediate steps."
|
||||
},
|
||||
{
|
||||
title: "Zoom in Editor",
|
||||
subtitle:
|
||||
"You can now zoom in/out of the editor using the Ctrl/Cmd + mouse wheel. This is separate from the default font size."
|
||||
},
|
||||
{
|
||||
title: "Improved search",
|
||||
subtitle:
|
||||
"Search now defaults to AND mode instead of OR mode which better matches the expected behavior. You can still use OR if you prefer the old behavior."
|
||||
}
|
||||
],
|
||||
cta: {
|
||||
|
||||
@@ -45,6 +45,7 @@ export const AuthenticationSettings: SettingsGroup[] = [
|
||||
title: strings.changePassword(),
|
||||
variant: "secondary",
|
||||
action: async () => {
|
||||
if (!(await createBackup())) return;
|
||||
const result = await showPasswordDialog({
|
||||
title: strings.changePassword(),
|
||||
message: strings.changePasswordDesc(),
|
||||
@@ -59,7 +60,6 @@ export const AuthenticationSettings: SettingsGroup[] = [
|
||||
}
|
||||
},
|
||||
validate: async ({ oldPassword, newPassword }) => {
|
||||
if (!(await createBackup())) return false;
|
||||
await db.user.clearSessions();
|
||||
return (
|
||||
(await db.user.changePassword(oldPassword, newPassword)) ||
|
||||
|
||||
@@ -30,8 +30,13 @@ import { Features } from "../../buy-dialog/features";
|
||||
import { ConfirmDialog } from "../../confirm";
|
||||
import { BuyDialog } from "../../buy-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { PromptDialog } from "../../prompt";
|
||||
|
||||
const PROVIDER_MAP = {
|
||||
0: "Streetwriters",
|
||||
1: "iOS",
|
||||
2: "Android",
|
||||
3: "Web"
|
||||
} as const;
|
||||
export function SubscriptionStatus() {
|
||||
const user = useUserStore((store) => store.user);
|
||||
|
||||
@@ -39,8 +44,7 @@ export function SubscriptionStatus() {
|
||||
await db.user.activateTrial();
|
||||
});
|
||||
|
||||
const provider =
|
||||
strings.subscriptionProviderInfo[user?.subscription?.provider || 0];
|
||||
const provider = PROVIDER_MAP[user?.subscription?.provider || 0];
|
||||
const {
|
||||
isTrial,
|
||||
isBeta,
|
||||
@@ -68,7 +72,7 @@ export function SubscriptionStatus() {
|
||||
const expiryDate = dayjs(user?.subscription?.expiry).format("MMMM D, YYYY");
|
||||
const startDate = dayjs(user?.subscription?.start).format("MMMM D, YYYY");
|
||||
return isPro
|
||||
? provider.type === "Streetwriters" || provider.type === "Gift card"
|
||||
? provider === "Streetwriters"
|
||||
? `Ending on ${expiryDate}`
|
||||
: `Next payment on ${expiryDate}.`
|
||||
: isProCancelled
|
||||
@@ -128,10 +132,10 @@ export function SubscriptionStatus() {
|
||||
: "Access only to basic features including unlimited notes & end-to-end encrypted syncing to unlimited devices."}
|
||||
</Text>
|
||||
<Text sx={{ mt: 2 }} variant="subBody">
|
||||
{subtitle}. {provider.desc()}
|
||||
{subtitle}
|
||||
</Text>
|
||||
<Flex sx={{ gap: 1, mt: 2 }}>
|
||||
{provider.type === "Web" && (isPro || isProCancelled) ? (
|
||||
{provider === "Web" && (isPro || isProCancelled) ? (
|
||||
<>
|
||||
{isPro && (
|
||||
<Button
|
||||
@@ -205,26 +209,6 @@ export function SubscriptionStatus() {
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
const giftCode = await PromptDialog.show({
|
||||
title: strings.redeemGiftCode(),
|
||||
description: strings.redeemGiftCodeDesc()
|
||||
});
|
||||
if (giftCode) {
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: strings.redeemingGiftCode(),
|
||||
subtitle: strings.pleaseWait() + "...",
|
||||
action: () => db.subscriptions.redeemCode(giftCode)
|
||||
}).catch((e) => showToast("error", e.message));
|
||||
}
|
||||
}}
|
||||
sx={{ bg: "background" }}
|
||||
>
|
||||
{strings.redeemGiftCode()}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -172,17 +172,6 @@ export class NNStorage implements IStorage {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
// noop
|
||||
generateCryptoKeyFallback(
|
||||
password: string,
|
||||
salt?: string
|
||||
): Promise<SerializedKey> {
|
||||
return this.generateCryptoKey(password, salt);
|
||||
}
|
||||
|
||||
// noop
|
||||
async deriveCryptoKeyFallback(): Promise<void> {}
|
||||
}
|
||||
|
||||
const dec = new TextDecoder();
|
||||
|
||||
@@ -49,7 +49,6 @@ import { hashNavigate } from "../navigation";
|
||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
import Vault from "../common/vault";
|
||||
import { Mutex } from "async-mutex";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
|
||||
export enum SaveState {
|
||||
NotSaved = -1,
|
||||
@@ -502,12 +501,6 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
const session = this.get().sessions.find((s) => s.id === id);
|
||||
if (!session) id = undefined;
|
||||
|
||||
const activeSession = this.getActiveSession();
|
||||
|
||||
if (activeSession) {
|
||||
this.saveSessionContentIfNotSaved(activeSession.id);
|
||||
}
|
||||
|
||||
if (
|
||||
id &&
|
||||
!settingStore.get().hideNoteTitle &&
|
||||
@@ -855,6 +848,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
sessionId
|
||||
});
|
||||
}
|
||||
|
||||
setDocumentTitle(
|
||||
settingStore.get().hideNoteTitle ? undefined : note.title
|
||||
);
|
||||
@@ -876,25 +870,6 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
});
|
||||
};
|
||||
|
||||
saveSessionContentIfNotSaved = (sessionId: string) => {
|
||||
const sessionSaveState = this.getSession(sessionId, ["default"])?.saveState;
|
||||
if (sessionSaveState === SaveState.NotSaved) {
|
||||
const editor = useEditorManager.getState().getEditor(sessionId);
|
||||
const content = editor?.editor?.getContent();
|
||||
this.saveSession(
|
||||
sessionId,
|
||||
content
|
||||
? {
|
||||
content: {
|
||||
data: content,
|
||||
type: "tiptap"
|
||||
}
|
||||
}
|
||||
: {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
newSession = () => {
|
||||
const state = useEditorStore.getState();
|
||||
const session = state.sessions.find((session) => session.type === "new");
|
||||
@@ -921,8 +896,6 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.saveSessionContentIfNotSaved(session.id);
|
||||
|
||||
db.fs().cancel(session.id).catch(console.error);
|
||||
if (state.history.includes(session.id))
|
||||
state.history.splice(state.history.indexOf(session.id), 1);
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
import { path } from "@notesnook-importer/core/dist/src/utils/path";
|
||||
import { type ZipEntry } from "./streams/unzip-stream";
|
||||
import { hashBuffer, writeEncryptedFile } from "../interfaces/fs";
|
||||
import { Notebook as NotebookType } from "@notesnook/core";
|
||||
|
||||
export async function* importFiles(zipFiles: File[]) {
|
||||
const { createUnzipIterator } = await import("./streams/unzip-stream");
|
||||
@@ -220,29 +219,26 @@ async function importLegacyNotebook(
|
||||
|
||||
async function importNotebook(
|
||||
notebook: Notebook,
|
||||
parent?: NotebookType
|
||||
parentId?: string
|
||||
): Promise<string[]> {
|
||||
if (!notebook) return [];
|
||||
|
||||
const selector = parent
|
||||
? db.relations.from(parent, "notebook").selector
|
||||
: db.notebooks.roots;
|
||||
let nb = await selector.find((eb) =>
|
||||
eb("notebooks.title", "==", notebook.title)
|
||||
);
|
||||
if (!nb) {
|
||||
const id = await db.notebooks.add({
|
||||
const id =
|
||||
(await db.notebooks.find(notebook.title))?.id ||
|
||||
(await db.notebooks.add({
|
||||
title: notebook.title
|
||||
});
|
||||
if (!id) return [];
|
||||
nb = await db.notebooks.notebook(id);
|
||||
if (parent && nb) await db.relations.add(parent, nb);
|
||||
}
|
||||
if (!nb) return [];
|
||||
if (notebook.children.length === 0) return [nb.id];
|
||||
}));
|
||||
if (!id) throw new Error(`Failed to import notebook: ${notebook.title}`);
|
||||
|
||||
const assignedNotebooks: string[] = [];
|
||||
for (const child of notebook.children || [])
|
||||
assignedNotebooks.push(...(await importNotebook(child, nb)));
|
||||
if (parentId)
|
||||
await db.relations.add(
|
||||
{ type: "notebook", id: parentId },
|
||||
{ type: "notebook", id: id }
|
||||
);
|
||||
|
||||
const assignedNotebooks: string[] = notebook.children.length > 0 ? [] : [id];
|
||||
for (const child of notebook.children || []) {
|
||||
assignedNotebooks.push(...(await importNotebook(child, id)));
|
||||
}
|
||||
return assignedNotebooks;
|
||||
}
|
||||
|
||||
@@ -310,8 +310,7 @@ function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
sx={{
|
||||
color: method.isDangerous
|
||||
? "var(--paragraph-error)"
|
||||
: "var(--paragraph-secondary)",
|
||||
whiteSpace: "pre-wrap"
|
||||
: "var(--paragraph-secondary)"
|
||||
}}
|
||||
>
|
||||
{method.description()}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: Gift cards
|
||||
description: Gift your friends and family a Notesnook Pro subscription.
|
||||
---
|
||||
|
||||
# Gift cards
|
||||
|
||||
Gift cards allow you to gift anyone a free Notesnook Pro subscription. It's a simple 3-step process:
|
||||
|
||||
1. You purchase a gift card.
|
||||
2. You send someone the gift code.
|
||||
3. They redeem it and claim the benefits.
|
||||
|
||||
## Purchasing a gift card
|
||||
|
||||
You can purchase a gift card from [https://notesnook.com/giftcards](https://notesnook.com/giftcards). There are 3 gift cards currently:
|
||||
|
||||
- 1 year gift card
|
||||
- 3 years gift card
|
||||
- 5 years gift card
|
||||
|
||||
> info
|
||||
>
|
||||
> Gift cards are not attached to a user account and can be claimed by any Notesnook user.
|
||||
|
||||
## Redeem a gift code
|
||||
|
||||
> info
|
||||
>
|
||||
> You won't be able to redeem a gift code if you already have a Notesnook subscription (even if its cancelled).
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to `Settings`
|
||||
2. Go to `Subscription settings`
|
||||
3. Click on `Redeem a gift code` button
|
||||
4. Enter the gift code you received
|
||||
5. Click on `Submit` and wait for the app to verify your gift code.
|
||||
6. Once the process succeeds, you should be upgraded to Pro.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to `Settings`
|
||||
2. Go to `Account settings`
|
||||
3. Click on `Redeem a gift code`
|
||||
4. Enter the gift code you received
|
||||
5. Click on `Redeem` and wait for the app to verify your gift code.
|
||||
6. Once the process succeeds, you should be upgraded to Pro.
|
||||
|
||||
---
|
||||
|
||||
> info
|
||||
>
|
||||
> Once you redeem a gift code, the person who purchased it will receive an email informing them that one of their gift code was claimed. The email **does not** contain any information about who claimed the gift code.
|
||||
|
||||
## FAQs
|
||||
|
||||
### Who can redeem a gift code?
|
||||
|
||||
Anyone who doesn't have a Notesnook Pro subscription can redeem a gift code and upgrade their account. One user can only redeem a single gift code at a time.
|
||||
|
||||
### Can I use a gift card to extend my subscription?
|
||||
|
||||
No.
|
||||
|
||||
### Is there an expiry date to a gift code?
|
||||
|
||||
Yes, these gift codes will expire after 1 year from the time of purchase.
|
||||
|
||||
### What can I do with a gift code?
|
||||
|
||||
Whatever you like. Sell it, gift it, use it.
|
||||
|
||||
### Can I refund a gift code I purchased?
|
||||
|
||||
No. Gift codes are non-refundable.
|
||||
|
||||
### Are gift cards auto-renewable?
|
||||
|
||||
No. Gift cards are a one-time purchase.
|
||||
|
||||
### Can I use cryptocurrency to purchase a gift card?
|
||||
|
||||
Currently, no. But we are actively working on a solution to support this so stay tuned.
|
||||
@@ -55,7 +55,6 @@ navigation:
|
||||
- path: recovering-your-account.md
|
||||
- path: deleting-your-account.md
|
||||
- path: app-lock.md
|
||||
- path: gift-cards.md
|
||||
|
||||
- path: privacy-mode.md
|
||||
- path: web-clipper
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
- 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!
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { match } from "fuzzyjs";
|
||||
import Database from "./index.js";
|
||||
import { Item, Note, SortOptions, TrashItem } from "../types.js";
|
||||
import { Item, Note, TrashItem } from "../types.js";
|
||||
import { DatabaseSchema, RawDatabaseSchema } from "../database/index.js";
|
||||
import { AnyColumnWithTable, Kysely, sql } from "@streetwriters/kysely";
|
||||
import { FilteredSelector } from "../database/sql-collection.js";
|
||||
@@ -27,7 +27,6 @@ import { VirtualizedGrouping } from "../utils/virtualized-grouping.js";
|
||||
import { logger } from "../logger.js";
|
||||
import { rebuildSearchIndex } from "../database/fts.js";
|
||||
import { transformQuery } from "../utils/query-transformer.js";
|
||||
import { getSortSelectors } from "../utils/grouping.js";
|
||||
|
||||
type SearchResults<T> = {
|
||||
sorted: (limit?: number) => Promise<VirtualizedGrouping<T>>;
|
||||
@@ -44,7 +43,7 @@ export default class Lookup {
|
||||
constructor(private readonly db: Database) {}
|
||||
|
||||
notes(query: string, notes?: FilteredSelector<Note>): SearchResults<Note> {
|
||||
return this.toSearchResults(async (limit, sortOptions) => {
|
||||
return this.toSearchResults(async (limit) => {
|
||||
const db = this.db.sql() as unknown as Kysely<RawDatabaseSchema>;
|
||||
const excludedIds = this.db.trash.cache.notes;
|
||||
|
||||
@@ -72,16 +71,13 @@ export default class Lookup {
|
||||
)
|
||||
.where("data", "match", query)
|
||||
.select(["noteId as id", "rank"])
|
||||
.$castTo<{
|
||||
id: string;
|
||||
rank: number;
|
||||
}>()
|
||||
.$castTo<{ id: string; rank: number }>()
|
||||
)
|
||||
.as("results")
|
||||
)
|
||||
.select(["results.id"])
|
||||
.groupBy("results.id")
|
||||
.orderBy(sql`SUM(results.rank)`, sortOptions?.sortDirection || "desc")
|
||||
.orderBy(sql`SUM(results.rank)`, "desc")
|
||||
.$if(!!limit, (eb) => eb.limit(limit!))
|
||||
|
||||
// filter out ids that have no note against them
|
||||
@@ -123,17 +119,9 @@ export default class Lookup {
|
||||
}
|
||||
|
||||
trash(query: string): SearchResults<TrashItem> {
|
||||
const sortOptions: SortOptions = {
|
||||
sortBy: "dateDeleted",
|
||||
sortDirection: "desc"
|
||||
};
|
||||
return {
|
||||
sorted: async (limit?: number) => {
|
||||
const { ids, items } = await this.filterTrash(
|
||||
query,
|
||||
limit,
|
||||
sortOptions
|
||||
);
|
||||
const { ids, items } = await this.filterTrash(query, limit);
|
||||
return new VirtualizedGrouping<TrashItem>(
|
||||
ids.length,
|
||||
this.db.options.batchSize,
|
||||
@@ -147,7 +135,7 @@ export default class Lookup {
|
||||
);
|
||||
},
|
||||
items: async (limit?: number) => {
|
||||
const { items } = await this.filterTrash(query, limit, sortOptions);
|
||||
const { items } = await this.filterTrash(query, limit);
|
||||
return items;
|
||||
},
|
||||
ids: () => this.filterTrash(query).then(({ ids }) => ids)
|
||||
@@ -169,8 +157,7 @@ export default class Lookup {
|
||||
fields: FuzzySearchField<T>[]
|
||||
) {
|
||||
return this.toSearchResults(
|
||||
(limit, sortOptions) =>
|
||||
this.filter(selector, query, fields, limit, sortOptions),
|
||||
(limit) => this.filter(selector, query, fields, limit),
|
||||
selector
|
||||
);
|
||||
}
|
||||
@@ -179,8 +166,7 @@ export default class Lookup {
|
||||
selector: FilteredSelector<T>,
|
||||
query: string,
|
||||
fields: FuzzySearchField<T>[],
|
||||
limit?: number,
|
||||
sortOptions?: SortOptions
|
||||
limit?: number
|
||||
) {
|
||||
const results: Map<string, number> = new Map();
|
||||
const columns = fields.map((f) => f.column);
|
||||
@@ -197,46 +183,24 @@ export default class Lookup {
|
||||
}
|
||||
selector.fields([]);
|
||||
|
||||
const sorted = Array.from(results.entries());
|
||||
|
||||
if (!sortOptions)
|
||||
// || sortOptions.sortBy === "relevance")
|
||||
sorted.sort(
|
||||
// sortOptions?.sortDirection === "desc"
|
||||
// ? (a, b) => a[1] - b[1]
|
||||
// :
|
||||
(a, b) => b[1] - a[1]
|
||||
);
|
||||
|
||||
return sorted.map((a) => a[0]);
|
||||
return Array.from(results.entries())
|
||||
.sort((a, b) => a[1] - b[1])
|
||||
.map((a) => a[0]);
|
||||
}
|
||||
|
||||
private toSearchResults<T extends Item>(
|
||||
ids: (limit?: number, sortOptions?: SortOptions) => Promise<string[]>,
|
||||
ids: (limit?: number) => Promise<string[]>,
|
||||
selector: FilteredSelector<T>
|
||||
): SearchResults<T> {
|
||||
const sortOptions: SortOptions = {
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "desc"
|
||||
};
|
||||
return {
|
||||
sorted: async (limit?: number) =>
|
||||
this.toVirtualizedGrouping(
|
||||
await ids(limit, sortOptions),
|
||||
selector,
|
||||
sortOptions
|
||||
),
|
||||
items: async (limit?: number) =>
|
||||
this.toItems(await ids(limit, sortOptions), selector, sortOptions),
|
||||
this.toVirtualizedGrouping(await ids(limit), selector),
|
||||
items: async (limit?: number) => this.toItems(await ids(limit), selector),
|
||||
ids
|
||||
};
|
||||
}
|
||||
|
||||
private async filterTrash(
|
||||
query: string,
|
||||
limit?: number,
|
||||
sortOptions?: SortOptions
|
||||
) {
|
||||
private async filterTrash(query: string, limit?: number) {
|
||||
const items = await this.db.trash.all();
|
||||
|
||||
const results: Map<string, { rank: number; item: TrashItem }> = new Map();
|
||||
@@ -248,20 +212,10 @@ export default class Lookup {
|
||||
results.set(item.id, { rank: result.score, item });
|
||||
}
|
||||
}
|
||||
const sorted = Array.from(results.entries());
|
||||
|
||||
if (!sortOptions)
|
||||
// || sortOptions.sortBy === "relevance")
|
||||
sorted.sort(
|
||||
// sortOptions?.sortDirection === "desc"
|
||||
// ? (a, b) => a[1].rank - b[1].rank
|
||||
// :
|
||||
(a, b) => b[1].rank - a[1].rank
|
||||
);
|
||||
else {
|
||||
const selector = getSortSelectors(sortOptions)[sortOptions.sortDirection];
|
||||
sorted.sort((a, b) => selector(a[1].item, b[1].item));
|
||||
}
|
||||
const sorted = Array.from(results.entries()).sort(
|
||||
(a, b) => a[1].rank - b[1].rank
|
||||
);
|
||||
return {
|
||||
ids: sorted.map((a) => a[0]),
|
||||
items: sorted.map((a) => a[1].item)
|
||||
@@ -270,33 +224,28 @@ export default class Lookup {
|
||||
|
||||
private toVirtualizedGrouping<T extends Item>(
|
||||
ids: string[],
|
||||
selector: FilteredSelector<T>,
|
||||
sortOptions?: SortOptions
|
||||
selector: FilteredSelector<T>
|
||||
) {
|
||||
// if (sortOptions?.sortBy === "relevance") sortOptions = undefined;
|
||||
return new VirtualizedGrouping<T>(
|
||||
ids.length,
|
||||
this.db.options.batchSize,
|
||||
() => Promise.resolve(ids),
|
||||
async (start, end) => {
|
||||
const items = await selector.items(ids.slice(start, end), sortOptions);
|
||||
const items = await selector.records(ids);
|
||||
return {
|
||||
ids: ids.slice(start, end),
|
||||
items
|
||||
items: Object.values(items).slice(start, end)
|
||||
};
|
||||
}
|
||||
// (items) => groupArray(items, () => `${items.length} results`)
|
||||
);
|
||||
}
|
||||
|
||||
private toItems<T extends Item>(
|
||||
ids: string[],
|
||||
selector: FilteredSelector<T>,
|
||||
sortOptions?: SortOptions
|
||||
selector: FilteredSelector<T>
|
||||
) {
|
||||
if (!ids.length) return [];
|
||||
// if (sortOptions?.sortBy === "relevance") sortOptions = undefined;
|
||||
return selector.items(ids, sortOptions);
|
||||
return selector.items(ids);
|
||||
}
|
||||
|
||||
async rebuild() {
|
||||
|
||||
@@ -90,16 +90,4 @@ export default class Subscriptions {
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
async redeemCode(code: string) {
|
||||
const token = await this.tokenManager.getAccessToken();
|
||||
if (!token) return;
|
||||
return http.post.json(
|
||||
`${hosts.SUBSCRIPTIONS_HOST}/subscriptions/redeem`,
|
||||
{
|
||||
code
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ class UserManager {
|
||||
salt: user.salt
|
||||
});
|
||||
}
|
||||
await this.db.kv().write("usesFallbackPWHash", usesFallback);
|
||||
EV.publish(EVENTS.userLoggedIn, user);
|
||||
} catch (e) {
|
||||
await this.tokenManager.saveToken(token);
|
||||
@@ -332,7 +333,7 @@ class UserManager {
|
||||
`${constants.API_HOST}${ENDPOINTS.deleteUser}`,
|
||||
{
|
||||
password: await this.db.storage().hash(password, user.email, {
|
||||
usesFallback: await this.usesFallbackPWHash(password)
|
||||
usesFallback: await this.db.kv().read("usesFallbackPWHash")
|
||||
})
|
||||
},
|
||||
token
|
||||
@@ -484,12 +485,17 @@ class UserManager {
|
||||
type: "change_email",
|
||||
new_email: newEmail,
|
||||
password: await this.db.storage().hash(password, email, {
|
||||
usesFallback: await this.usesFallbackPWHash(password)
|
||||
usesFallback: await this.db.kv().read("usesFallbackPWHash")
|
||||
}),
|
||||
verification_code: code
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
await this.db.storage().deriveCryptoKey({
|
||||
password,
|
||||
salt: user.salt
|
||||
});
|
||||
}
|
||||
|
||||
recoverAccount(email: string) {
|
||||
@@ -540,11 +546,6 @@ class UserManager {
|
||||
|
||||
if (data.encryptionKey) await this.db.sync({ type: "fetch", force: true });
|
||||
|
||||
if (old_password)
|
||||
old_password = await this.db.storage().hash(old_password, email, {
|
||||
usesFallback: await this.usesFallbackPWHash(old_password)
|
||||
});
|
||||
|
||||
await this.db.storage().deriveCryptoKey({
|
||||
password: new_password,
|
||||
salt
|
||||
@@ -563,6 +564,10 @@ class UserManager {
|
||||
await this.updateUser({ attachmentsKey: user.attachmentsKey });
|
||||
}
|
||||
|
||||
if (old_password)
|
||||
old_password = await this.db.storage().hash(old_password, email, {
|
||||
usesFallback: await this.db.kv().read("usesFallbackPWHash")
|
||||
});
|
||||
if (new_password)
|
||||
new_password = await this.db.storage().hash(new_password, email);
|
||||
|
||||
@@ -575,33 +580,10 @@ class UserManager {
|
||||
},
|
||||
token
|
||||
);
|
||||
await this.db.kv().write("usesFallbackPWHash", false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async usesFallbackPWHash(password: string) {
|
||||
const user = await this.getUser();
|
||||
const encryptionKey = await this.getEncryptionKey();
|
||||
if (!user || !encryptionKey) return false;
|
||||
const fallbackCryptoKey = await this.db
|
||||
.storage()
|
||||
.generateCryptoKeyFallback(password, user.salt);
|
||||
if (!fallbackCryptoKey) return false;
|
||||
const cryptoKey = await this.db
|
||||
.storage()
|
||||
.generateCryptoKey(password, user.salt);
|
||||
|
||||
if (!encryptionKey.key || !fallbackCryptoKey.key || !cryptoKey.key)
|
||||
throw new Error("Failed to generate crypto keys.");
|
||||
|
||||
if (
|
||||
fallbackCryptoKey.key !== encryptionKey.key &&
|
||||
cryptoKey.key !== encryptionKey.key
|
||||
)
|
||||
throw new Error("Wrong password.");
|
||||
|
||||
return fallbackCryptoKey.key === encryptionKey.key;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserManager;
|
||||
|
||||
@@ -30,6 +30,7 @@ interface KV {
|
||||
deviceId: string;
|
||||
lastBackupTime: number;
|
||||
fullOfflineMode: boolean;
|
||||
usesFallbackPWHash: boolean;
|
||||
}
|
||||
|
||||
export const KEYS: (keyof KV)[] = [
|
||||
@@ -40,7 +41,8 @@ export const KEYS: (keyof KV)[] = [
|
||||
"monographs",
|
||||
"deviceId",
|
||||
"lastBackupTime",
|
||||
"fullOfflineMode"
|
||||
"fullOfflineMode",
|
||||
"usesFallbackPWHash"
|
||||
];
|
||||
|
||||
export class KVStorage {
|
||||
|
||||
@@ -402,9 +402,7 @@ export class FilteredSelector<T extends Item> {
|
||||
return (
|
||||
await this.filter
|
||||
.$if(!!sortOptions, (eb) =>
|
||||
eb.$call(
|
||||
this.buildSortExpression({ ...sortOptions!, groupBy: "none" })
|
||||
)
|
||||
eb.$call(this.buildSortExpression(sortOptions!))
|
||||
)
|
||||
.select("id")
|
||||
.execute()
|
||||
@@ -416,7 +414,7 @@ export class FilteredSelector<T extends Item> {
|
||||
return (await this.filter
|
||||
.$if(!!ids && ids.length > 0, (eb) => eb.where("id", "in", ids!))
|
||||
.$if(!!sortOptions, (eb) =>
|
||||
eb.$call(this.buildSortExpression({ ...sortOptions!, groupBy: "none" }))
|
||||
eb.$call(this.buildSortExpression(sortOptions!))
|
||||
)
|
||||
.$if(this._fields.length === 0, (eb) => eb.selectAll())
|
||||
.$if(this._fields.length > 0, (eb) => eb.select(this._fields))
|
||||
@@ -530,9 +528,7 @@ export class FilteredSelector<T extends Item> {
|
||||
if (options.groupBy === "abc") fields.push("title");
|
||||
else if (options.sortBy === "title" && options.groupBy !== "none")
|
||||
fields.push("dateCreated");
|
||||
else if (options.sortBy !== "dueDate")
|
||||
// && options.sortBy !== "relevance")
|
||||
fields.push(options.sortBy);
|
||||
else if (options.sortBy !== "dueDate") fields.push(options.sortBy);
|
||||
|
||||
return Array.from(
|
||||
groupArray(
|
||||
@@ -554,7 +550,7 @@ export class FilteredSelector<T extends Item> {
|
||||
() => this.ids(options),
|
||||
async (start, end) => {
|
||||
const items = (await this.filter
|
||||
.$call(this.buildSortExpression({ ...options, groupBy: "none" }))
|
||||
.$call(this.buildSortExpression(options))
|
||||
.offset(start)
|
||||
.limit(end - start)
|
||||
.selectAll()
|
||||
@@ -600,13 +596,18 @@ export class FilteredSelector<T extends Item> {
|
||||
}
|
||||
}
|
||||
|
||||
private buildSortExpression(options: GroupOptions, hasDueDate?: boolean) {
|
||||
private buildSortExpression(
|
||||
options: GroupOptions | SortOptions,
|
||||
hasDueDate?: boolean
|
||||
) {
|
||||
sanitizeSortOptions(this.type, options);
|
||||
|
||||
const sortBy: Set<SortOptions["sortBy"]> = new Set();
|
||||
if (options.groupBy === "abc") sortBy.add("title");
|
||||
else if (options.sortBy === "title" && options.groupBy !== "none")
|
||||
sortBy.add("dateCreated");
|
||||
if (isGroupOptions(options)) {
|
||||
if (options.groupBy === "abc") sortBy.add("title");
|
||||
else if (options.sortBy === "title" && options.groupBy !== "none")
|
||||
sortBy.add("dateCreated");
|
||||
}
|
||||
sortBy.add(options.sortBy);
|
||||
|
||||
return <T>(
|
||||
@@ -643,8 +644,7 @@ export class FilteredSelector<T extends Item> {
|
||||
(qb) => qb.parens(createUpcomingReminderTimeQuery()),
|
||||
options.sortDirection
|
||||
);
|
||||
} // if (item !== "relevance")
|
||||
else qb = qb.orderBy(item, options.sortDirection);
|
||||
} else qb = qb.orderBy(item, options.sortDirection);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,10 +66,6 @@ export interface IStorage {
|
||||
getCryptoKey(): Promise<string | undefined>;
|
||||
generateCryptoKey(password: string, salt?: string): Promise<SerializedKey>;
|
||||
|
||||
generateCryptoKeyFallback(
|
||||
password: string,
|
||||
salt?: string
|
||||
): Promise<SerializedKey>;
|
||||
deriveCryptoKeyFallback(credentials: SerializedKey): Promise<void>;
|
||||
|
||||
// async generateRandomKey() {
|
||||
|
||||
@@ -536,7 +536,7 @@ export type User = {
|
||||
cancelURL: string | null;
|
||||
expiry: number;
|
||||
productId: string;
|
||||
provider: 0 | 1 | 2 | 3 | 4;
|
||||
provider: 0 | 1 | 2 | 3;
|
||||
start: number;
|
||||
type: 0 | 1 | 2 | 5 | 6 | 7;
|
||||
updateURL: string | null;
|
||||
|
||||
@@ -18,13 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isReminderActive } from "../collections/reminders.js";
|
||||
import {
|
||||
GroupHeader,
|
||||
GroupOptions,
|
||||
ItemType,
|
||||
Reminder,
|
||||
SortOptions
|
||||
} from "../types.js";
|
||||
import { GroupHeader, GroupOptions, ItemType, Reminder } from "../types.js";
|
||||
import { getWeekGroupFromTimestamp, MONTHS_FULL } from "../utils/date.js";
|
||||
|
||||
type PartialGroupableItem = {
|
||||
@@ -39,7 +33,7 @@ type PartialGroupableItem = {
|
||||
export type GroupKeySelectorFunction<T> = (item: T) => string;
|
||||
|
||||
export const getSortValue = (
|
||||
options: SortOptions | undefined,
|
||||
options: GroupOptions | undefined,
|
||||
item: PartialGroupableItem
|
||||
) => {
|
||||
if (
|
||||
@@ -59,7 +53,7 @@ export const getSortValue = (
|
||||
};
|
||||
|
||||
export function getSortSelectors<T extends PartialGroupableItem>(
|
||||
options: SortOptions
|
||||
options: GroupOptions
|
||||
) {
|
||||
if (options.sortBy === "title")
|
||||
return {
|
||||
|
||||
@@ -230,9 +230,6 @@ export function TaskListComponent(
|
||||
ref={forwardRef}
|
||||
dir={textDirection}
|
||||
contentEditable={editor.isEditable && !readonly}
|
||||
onPaste={(e) => {
|
||||
if (readonly) e.preventDefault();
|
||||
}}
|
||||
sx={{
|
||||
ul: {
|
||||
display: "block",
|
||||
|
||||
@@ -32,11 +32,7 @@ import {
|
||||
findParentNodeClosestToPos,
|
||||
getExactChangedNodes
|
||||
} from "../../utils/prosemirror.js";
|
||||
import {
|
||||
countCheckedItems,
|
||||
findRootTaskList,
|
||||
toggleChildren
|
||||
} from "./utils.js";
|
||||
import { countCheckedItems, findRootTaskList, toggleChildren } from "./utils.js";
|
||||
import { Node as ProsemirrorNode } from "@tiptap/pm/model";
|
||||
import { TaskItemNode } from "../task-item/index.js";
|
||||
|
||||
@@ -53,16 +49,7 @@ export const TaskListNode = TaskList.extend({
|
||||
return {
|
||||
stats: {
|
||||
default: { checked: 0, total: 0 },
|
||||
rendered: false,
|
||||
parseHTML: (element) => {
|
||||
// do not update stats for nested task lists
|
||||
if (!!element.closest("ul")) return { checked: 0, total: 0 };
|
||||
const total = element.querySelectorAll("li.checklist--item").length;
|
||||
const checked = element.querySelectorAll(
|
||||
"li.checklist--item.checked"
|
||||
).length;
|
||||
return { checked, total };
|
||||
}
|
||||
rendered: false
|
||||
},
|
||||
title: {
|
||||
default: null,
|
||||
@@ -235,6 +222,26 @@ export const TaskListNode = TaskList.extend({
|
||||
// the task list.
|
||||
new Plugin({
|
||||
key: new PluginKey("task-list-state-management"),
|
||||
view(view) {
|
||||
const { tr } = view.state;
|
||||
tr.doc.descendants((node, pos) => {
|
||||
if (node.type.name === TaskList.name) {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
stats: countCheckedItems(node)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
tr.setMeta("preventUpdate", true);
|
||||
tr.setMeta("addToHistory", false);
|
||||
try {
|
||||
view.dispatch(tr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return {};
|
||||
},
|
||||
appendTransaction(transactions, oldState, newState) {
|
||||
if (!transactions[0].docChanged) return;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
useToolbarStore
|
||||
} from "./stores/toolbar-store.js";
|
||||
import { ToolbarDefinition } from "./types.js";
|
||||
import { ScrollContainer } from "@notesnook/ui";
|
||||
|
||||
type ToolbarProps = FlexProps & {
|
||||
editor: Editor;
|
||||
@@ -89,34 +90,44 @@ export function Toolbar(props: ToolbarProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
className={["editor-toolbar", className].join(" ")}
|
||||
sx={{
|
||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
||||
overflowX: isMobile ? "auto" : "hidden",
|
||||
bg: "background",
|
||||
borderRadius: isMobile ? "0px" : "default",
|
||||
...sx
|
||||
}}
|
||||
{...flexProps}
|
||||
>
|
||||
{toolbarTools.map((tools) => {
|
||||
return (
|
||||
<ToolbarGroup
|
||||
key={tools.join("")}
|
||||
tools={tools}
|
||||
editor={editor}
|
||||
groupId={tools.join("")}
|
||||
sx={{
|
||||
borderRight: "1px solid var(--separator)",
|
||||
":last-of-type": { borderRight: "none" },
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
<ScrollContainer
|
||||
className="tabsScroll"
|
||||
suppressScrollY
|
||||
style={{ flex: 1 }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
pointerEvents: "none"
|
||||
})}
|
||||
</Flex>
|
||||
<EditorFloatingMenus editor={editor} />
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
>
|
||||
<Flex
|
||||
className={["editor-toolbar", className].join(" ")}
|
||||
sx={{
|
||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
||||
bg: "background",
|
||||
borderRadius: isMobile ? "0px" : "default",
|
||||
...sx
|
||||
}}
|
||||
{...flexProps}
|
||||
>
|
||||
{toolbarTools.map((tools) => {
|
||||
return (
|
||||
<ToolbarGroup
|
||||
key={tools.join("")}
|
||||
tools={tools}
|
||||
editor={editor}
|
||||
groupId={tools.join("")}
|
||||
sx={{
|
||||
borderRight: "1px solid var(--separator)",
|
||||
":last-of-type": { borderRight: "none" },
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<EditorFloatingMenus editor={editor} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -663,7 +663,7 @@ $headline$: Use starting line of the note as title.`,
|
||||
reloadEditor: () => t`Taking too long? Reload editor`,
|
||||
copy: () => t`Copy`,
|
||||
resendCode: (seconds?: number) =>
|
||||
t`Resend code${seconds ? ` in ${seconds}` : ""}`,
|
||||
t`Resend code${seconds ? `in ${seconds}` : ""}`,
|
||||
change2faMethod: () => t`Change 2FA method`,
|
||||
copyCodes: () => t`Copy codes`,
|
||||
saveToFile: () => t`Save to file`,
|
||||
@@ -678,12 +678,6 @@ $headline$: Use starting line of the note as title.`,
|
||||
monthShort: () => t`mo`,
|
||||
yearShort: () => t`yr`,
|
||||
subscriptionProviderInfo: {
|
||||
0: {
|
||||
type: "Streetwriters",
|
||||
title: () => t`Subscription awarded from Streetwriters`,
|
||||
desc: () =>
|
||||
t`You were awarded a subscription to Notesnook Pro by Streetwriters.`
|
||||
},
|
||||
1: {
|
||||
type: "iOS",
|
||||
title: () => t`Subscribed on iOS`,
|
||||
@@ -703,11 +697,6 @@ $headline$: Use starting line of the note as title.`,
|
||||
title: () => t`Subscribed on Web`,
|
||||
desc: () => t`You subscribed to Notesnook Pro on the Web/Desktop App.`,
|
||||
icon: "web"
|
||||
},
|
||||
4: {
|
||||
type: "Gift card",
|
||||
title: () => t`Subscribed using gift card`,
|
||||
desc: () => t`You subscribed to Notesnook Pro using a gift card.`
|
||||
}
|
||||
},
|
||||
dark: () => t`Dark`,
|
||||
@@ -1712,12 +1701,11 @@ For example:
|
||||
]().toLocaleLowerCase()} in case you lose access to your ${strings[
|
||||
primary
|
||||
]().toLocaleLowerCase()}.`,
|
||||
transactionStatusToText: (
|
||||
key: keyof typeof TRANSACTION_STATUS | ({} & string)
|
||||
) => {
|
||||
return key in TRANSACTION_STATUS
|
||||
? TRANSACTION_STATUS[key as keyof typeof TRANSACTION_STATUS]()
|
||||
: key;
|
||||
transactionStatusToText: {
|
||||
completed: () => t`Completed`,
|
||||
refunded: () => t`"Refunded`,
|
||||
partially_refunded: () => t`Partially refunded`,
|
||||
disputed: () => t`Disputed`
|
||||
},
|
||||
viewReceipt: () => t`View receipt`,
|
||||
customDictWords: (count: number) =>
|
||||
@@ -2401,9 +2389,5 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
one: `Move all notes in this notebook to trash`,
|
||||
other: `Move all notes in these notebooks to trash`
|
||||
}),
|
||||
emailCopied: () => t`Email copied`,
|
||||
redeemGiftCode: () => t`Redeem gift code`,
|
||||
redeemGiftCodeDesc: () => t`Enter the gift code to redeem your subscription.`,
|
||||
redeemingGiftCode: () => t`Redeeming gift code`,
|
||||
redeem: () => t`Redeem`
|
||||
emailCopied: () => t`Email copied`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user