mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
12 Commits
v3.0.12
...
fix-share-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a66bc9a8e2 | ||
|
|
2c729f90cc | ||
|
|
800514d769 | ||
|
|
b03b9482dc | ||
|
|
9019490dbd | ||
|
|
4a3e23c288 | ||
|
|
1800ebea2d | ||
|
|
a708c9e9eb | ||
|
|
2aeb875fec | ||
|
|
a8164e9f6c | ||
|
|
0ee8e738e5 | ||
|
|
8fdef2ac7d |
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.12",
|
||||
"version": "3.0.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.12",
|
||||
"version": "3.0.13",
|
||||
"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.12",
|
||||
"version": "3.0.13",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -25,7 +25,6 @@ import { I18nManager, View } from "react-native";
|
||||
import "react-native-gesture-handler";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
import { initializeLogger } from "./common/database/logger";
|
||||
import AppLockedOverlay from "./components/app-lock-overlay";
|
||||
import { withErrorBoundry } from "./components/exception-handler";
|
||||
import GlobalSafeAreaProvider from "./components/globalsafearea";
|
||||
@@ -41,33 +40,24 @@ import { useUserStore } from "./stores/use-user-store";
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
const { appLockEnabled, appLockMode } = SettingsService.get();
|
||||
if (appLockEnabled || appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const init = useAppEvents();
|
||||
useAppEvents();
|
||||
//@ts-ignore
|
||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||
useEffect(() => {
|
||||
initializeLogger()
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
})
|
||||
.finally(() => {
|
||||
const { appLockEnabled, appLockMode } = SettingsService.get();
|
||||
if (appLockEnabled || appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||
init();
|
||||
setTimeout(async () => {
|
||||
SettingsService.onFirstLaunch();
|
||||
await Notifications.get();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(true);
|
||||
}
|
||||
TipManager.init();
|
||||
}, 100);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
SettingsService.onFirstLaunch();
|
||||
setTimeout(async () => {
|
||||
await Notifications.get();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(true);
|
||||
}
|
||||
TipManager.init();
|
||||
}, 100);
|
||||
}, []);
|
||||
return (
|
||||
<View
|
||||
|
||||
@@ -23,7 +23,9 @@ import { Platform } from "react-native";
|
||||
import { setLogger } from ".";
|
||||
import { RNSqliteDriver } from "./sqlite.kysely";
|
||||
|
||||
let loggerLoaded = false;
|
||||
const initializeLogger = async () => {
|
||||
if (loggerLoaded) return;
|
||||
await initialize({
|
||||
dialect: (name) => ({
|
||||
createDriver: () => {
|
||||
@@ -37,6 +39,7 @@ const initializeLogger = async () => {
|
||||
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL"
|
||||
});
|
||||
setLogger();
|
||||
loggerLoaded = true;
|
||||
};
|
||||
|
||||
export { initializeLogger };
|
||||
|
||||
@@ -18,7 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import { AppStateStatus, Platform, TextInput, View } from "react-native";
|
||||
import {
|
||||
AppStateStatus,
|
||||
Platform,
|
||||
TextInput,
|
||||
useWindowDimensions,
|
||||
View
|
||||
} from "react-native";
|
||||
//@ts-ignore
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { DatabaseLogger } from "../../common/database";
|
||||
@@ -46,6 +52,7 @@ import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
|
||||
const getUser = () => {
|
||||
const user = MMKV.getString("user");
|
||||
@@ -86,6 +93,7 @@ const AppLockedOverlay = () => {
|
||||
const appState = useAppState();
|
||||
const lastAppState = useRef<AppStateStatus>(appState);
|
||||
const biometricUnlockAwaitingUserInput = useRef(false);
|
||||
const { height } = useWindowDimensions();
|
||||
const keyboardType = useSettingStore(
|
||||
(state) => state.settings.applockKeyboardType
|
||||
);
|
||||
@@ -191,15 +199,20 @@ const AppLockedOverlay = () => {
|
||||
}, [appState, onUnlockAppRequested, appLocked]);
|
||||
|
||||
return appLocked ? (
|
||||
<View
|
||||
<KeyboardAwareScrollView
|
||||
style={{
|
||||
backgroundColor: colors.primary.background,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
justifyContent: "center"
|
||||
zIndex: 999
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
justifyContent: "center",
|
||||
minHeight: height
|
||||
}}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Toast context="local" />
|
||||
<View
|
||||
@@ -314,7 +327,7 @@ const AppLockedOverlay = () => {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
) : null;
|
||||
};
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ import {
|
||||
import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { sleep } from "../utils/time";
|
||||
import { initializeLogger } from "../common/database/logger";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -280,8 +281,108 @@ const onSubscriptionError = async (error: RNIap.PurchaseError) => {
|
||||
|
||||
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
|
||||
|
||||
const doAppLoadActions = async () => {
|
||||
if (SettingsService.get().sessionExpired) {
|
||||
eSendEvent(eLoginSessionExpired);
|
||||
return;
|
||||
}
|
||||
notifee.setBadgeCount(0);
|
||||
|
||||
if (!(await db.user.getUser())) {
|
||||
setLoginMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
await useMessageStore.getState().setAnnouncement();
|
||||
if (NewFeature.present()) return;
|
||||
if (await checkAppUpdateAvailable()) return;
|
||||
if (await checkForRateAppRequest()) return;
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
if (SettingsService.get().introCompleted) {
|
||||
useMessageStore.subscribe((state) => {
|
||||
const dialogs = state.dialogs;
|
||||
if (dialogs.length > 0) {
|
||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkAppUpdateAvailable = async () => {
|
||||
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
|
||||
return;
|
||||
try {
|
||||
const version =
|
||||
Config.GITHUB_RELEASE === "true"
|
||||
? await getGithubVersion()
|
||||
: await checkVersion();
|
||||
if (!version || !version?.needsUpdate) return false;
|
||||
|
||||
setUpdateAvailableMessage(version);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkForRateAppRequest = async () => {
|
||||
const rateApp = SettingsService.get().rateApp as number;
|
||||
if (
|
||||
rateApp &&
|
||||
rateApp < Date.now() &&
|
||||
!useMessageStore.getState().message?.visible
|
||||
) {
|
||||
setRateAppMessage();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const IsDatabaseMigrationRequired = () => {
|
||||
if (!db.migrations.required() || useUserStore.getState().appLocked)
|
||||
return false;
|
||||
|
||||
presentSheet({
|
||||
component: <Migrate />,
|
||||
onClose: () => {
|
||||
if (!db.migrations.required()) {
|
||||
initializeDatabase();
|
||||
}
|
||||
},
|
||||
disableClosing: true
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
const initializeDatabase = async (password?: string) => {
|
||||
if (useUserStore.getState().appLocked) return;
|
||||
if (!db.isInitialized) {
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
try {
|
||||
await setupDatabase(password);
|
||||
await db.init();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error);
|
||||
ToastManager.error(e as Error, "Error initializing database", "global");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDatabaseMigrationRequired()) return;
|
||||
|
||||
if (db.isInitialized) {
|
||||
Notifications.setupReminders(true);
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
DatabaseLogger.info("Database initialized");
|
||||
}
|
||||
Walkthrough.init();
|
||||
};
|
||||
|
||||
export const useAppEvents = () => {
|
||||
const loading = useSettingStore((state) => state.isAppLoading);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
|
||||
state.setLastSynced,
|
||||
state.setUser,
|
||||
@@ -312,7 +413,7 @@ export const useAppEvents = () => {
|
||||
}, [setLastSynced]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
if (isAppLoading) return;
|
||||
|
||||
let subscriptions: EventManagerSubscription[] = [];
|
||||
const eventManager = db.eventManager;
|
||||
@@ -327,7 +428,7 @@ export const useAppEvents = () => {
|
||||
return () => {
|
||||
subscriptions.forEach((sub) => sub?.unsubscribe?.());
|
||||
};
|
||||
}, [loading, onSyncComplete]);
|
||||
}, [isAppLoading, onSyncComplete]);
|
||||
|
||||
const subscribeToPurchaseListeners = useCallback(async () => {
|
||||
if (Platform.OS === "android") {
|
||||
@@ -572,7 +673,7 @@ export const useAppEvents = () => {
|
||||
});
|
||||
}
|
||||
let sub: NativeEventSubscription;
|
||||
if (!loading && !appLocked) {
|
||||
if (!isAppLoading && !appLocked) {
|
||||
setTimeout(() => {
|
||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||
if (
|
||||
@@ -594,7 +695,7 @@ export const useAppEvents = () => {
|
||||
sub?.remove();
|
||||
unsubscribePurchaseListeners();
|
||||
};
|
||||
}, [loading, appLocked, checkAutoBackup]);
|
||||
}, [isAppLoading, appLocked, checkAutoBackup]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appLocked && !syncing && refValues.current.backupDidWait) {
|
||||
@@ -643,127 +744,22 @@ export const useAppEvents = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
if (!isAppLoading) {
|
||||
onUserUpdated();
|
||||
doAppLoadActions();
|
||||
}
|
||||
}, [loading, onUserUpdated]);
|
||||
|
||||
const initializeDatabase = useCallback(async (password?: string) => {
|
||||
const IsDatabaseMigrationRequired = () => {
|
||||
if (!db.migrations.required() || useUserStore.getState().appLocked)
|
||||
return false;
|
||||
|
||||
presentSheet({
|
||||
component: <Migrate />,
|
||||
onClose: () => {
|
||||
if (!db.migrations.required()) {
|
||||
initializeDatabase();
|
||||
}
|
||||
},
|
||||
disableClosing: true
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
if (useUserStore.getState().appLocked) return;
|
||||
if (!db.isInitialized) {
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
try {
|
||||
await setupDatabase(password);
|
||||
await db.init();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error);
|
||||
ToastManager.error(e as Error, "Error initializing database", "global");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDatabaseMigrationRequired()) return;
|
||||
|
||||
if (db.isInitialized) {
|
||||
Notifications.setupReminders(true);
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
DatabaseLogger.info("Database initialized");
|
||||
}
|
||||
Walkthrough.init();
|
||||
}, []);
|
||||
}, [isAppLoading, onUserUpdated]);
|
||||
|
||||
useEffect(() => {
|
||||
let sub: () => void;
|
||||
if (appLocked) {
|
||||
const sub = useUserStore.subscribe((state) => {
|
||||
if (!state.appLocked && useSettingStore.getState().isAppLoading) {
|
||||
console.log("DB initialized");
|
||||
if (!appLocked && isAppLoading) {
|
||||
initializeLogger()
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
})
|
||||
.finally(() => {
|
||||
//@ts-ignore
|
||||
initializeDatabase();
|
||||
sub();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
sub?.();
|
||||
};
|
||||
}, [appLocked, initializeDatabase]);
|
||||
|
||||
return initializeDatabase;
|
||||
};
|
||||
|
||||
const doAppLoadActions = async () => {
|
||||
if (SettingsService.get().sessionExpired) {
|
||||
eSendEvent(eLoginSessionExpired);
|
||||
return;
|
||||
}
|
||||
notifee.setBadgeCount(0);
|
||||
|
||||
if (!(await db.user.getUser())) {
|
||||
setLoginMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
await useMessageStore.getState().setAnnouncement();
|
||||
if (NewFeature.present()) return;
|
||||
if (await checkAppUpdateAvailable()) return;
|
||||
if (await checkForRateAppRequest()) return;
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
if (SettingsService.get().introCompleted) {
|
||||
useMessageStore.subscribe((state) => {
|
||||
const dialogs = state.dialogs;
|
||||
if (dialogs.length > 0) {
|
||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkAppUpdateAvailable = async () => {
|
||||
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
|
||||
return;
|
||||
try {
|
||||
const version =
|
||||
Config.GITHUB_RELEASE === "true"
|
||||
? await getGithubVersion()
|
||||
: await checkVersion();
|
||||
if (!version || !version?.needsUpdate) return false;
|
||||
|
||||
setUpdateAvailableMessage(version);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkForRateAppRequest = async () => {
|
||||
const rateApp = SettingsService.get().rateApp as number;
|
||||
if (
|
||||
rateApp &&
|
||||
rateApp < Date.now() &&
|
||||
!useMessageStore.getState().message?.visible
|
||||
) {
|
||||
setRateAppMessage();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}, [appLocked, isAppLoading]);
|
||||
};
|
||||
|
||||
@@ -62,7 +62,6 @@ import {
|
||||
} from "../services/event-manager";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eOnEnterEditor,
|
||||
eOnExitEditor,
|
||||
@@ -286,9 +285,10 @@ const _TabsHolder = () => {
|
||||
break;
|
||||
case "mobile":
|
||||
if (
|
||||
state &&
|
||||
!state?.movedAway &&
|
||||
useTabStore.getState().getCurrentNoteId()
|
||||
(state &&
|
||||
!state?.movedAway &&
|
||||
useTabStore.getState().getCurrentNoteId()) ||
|
||||
editorState().movedAway === false
|
||||
) {
|
||||
tabBarRef.current?.goToIndex(2, false);
|
||||
} else {
|
||||
|
||||
@@ -141,7 +141,6 @@ const onBoot = async () => {
|
||||
}
|
||||
|
||||
await Notifications.setupReminders();
|
||||
SettingsService.init();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,6 @@ async function run(progress = false, context) {
|
||||
}
|
||||
|
||||
let path;
|
||||
let backupFilePath;
|
||||
let backupFileName = "notesnook_backup_" + Date.now();
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
@@ -236,7 +235,7 @@ async function run(progress = false, context) {
|
||||
await sleep(300);
|
||||
|
||||
if (showBackupCompleteSheet) {
|
||||
presentBackupCompleteSheet(backupFilePath);
|
||||
presentBackupCompleteSheet(path);
|
||||
} else {
|
||||
progress && eSendEvent(eCloseSheet);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Profile } from "@notesnook/core";
|
||||
import { User } from "@notesnook/core";
|
||||
import { Profile, User } from "@notesnook/core";
|
||||
import create, { State } from "zustand";
|
||||
|
||||
export enum SyncStatus {
|
||||
@@ -56,7 +55,9 @@ export const useUserStore = create<UserStore>((set) => ({
|
||||
set({ syncing: syncing, lastSyncStatus: status });
|
||||
},
|
||||
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
|
||||
lockApp: (appLocked) => set({ appLocked }),
|
||||
lockApp: (appLocked) => {
|
||||
set({ appLocked });
|
||||
},
|
||||
lastSyncStatus: SyncStatus.Never,
|
||||
disableAppLockRequests: false,
|
||||
setDisableAppLockRequests: (disableAppLockRequests) => {
|
||||
|
||||
16
apps/mobile/package-lock.json
generated
16
apps/mobile/package-lock.json
generated
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"../../packages/common": {
|
||||
"name": "@notesnook/common",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/core": "^8.0.0",
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"../../packages/core": {
|
||||
"name": "@notesnook/core",
|
||||
"version": "8.0.2",
|
||||
"version": "8.0.8",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
@@ -3121,7 +3121,7 @@
|
||||
},
|
||||
"../../packages/crypto": {
|
||||
"name": "@notesnook/crypto",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"dev": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -3135,7 +3135,7 @@
|
||||
},
|
||||
"../../packages/editor": {
|
||||
"name": "@notesnook/editor",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -23800,13 +23800,13 @@
|
||||
},
|
||||
"../../packages/logger": {
|
||||
"name": "@notesnook/logger",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {}
|
||||
},
|
||||
"../../packages/sodium": {
|
||||
"name": "@notesnook/sodium",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"dev": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
@@ -25178,7 +25178,7 @@
|
||||
},
|
||||
"../../packages/theme": {
|
||||
"name": "@notesnook/theme",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@emotion/react": "11.11.1",
|
||||
@@ -26245,7 +26245,7 @@
|
||||
},
|
||||
"../../packages/ui": {
|
||||
"name": "@notesnook/ui",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.8",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/theme": "file:../theme"
|
||||
|
||||
6
apps/theme-builder/package-lock.json
generated
6
apps/theme-builder/package-lock.json
generated
@@ -134,7 +134,7 @@
|
||||
},
|
||||
"../../packages/common": {
|
||||
"name": "@notesnook/common",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/core": "^8.0.0",
|
||||
@@ -330,7 +330,7 @@
|
||||
},
|
||||
"../../packages/theme": {
|
||||
"name": "@notesnook/theme",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@emotion/react": "11.11.1",
|
||||
@@ -979,7 +979,7 @@
|
||||
},
|
||||
"../web": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.10",
|
||||
"version": "3.0.13",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
2
apps/vericrypt/package-lock.json
generated
2
apps/vericrypt/package-lock.json
generated
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"../../packages/crypto": {
|
||||
"name": "@notesnook/crypto",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/sodium": "file:../sodium"
|
||||
|
||||
6
apps/web/package-lock.json
generated
6
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.12",
|
||||
"version": "3.0.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.12",
|
||||
"version": "3.0.13",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -31906,7 +31906,7 @@
|
||||
},
|
||||
"../desktop": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.11",
|
||||
"version": "3.0.13",
|
||||
"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.12",
|
||||
"version": "3.0.13",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useEffect } from "react";
|
||||
import { useStore } from "./stores/app-store";
|
||||
import { useStore as useUserStore } from "./stores/user-store";
|
||||
import { useStore as useAttachmentStore } from "./stores/attachment-store";
|
||||
import { useEditorStore } from "./stores/editor-store";
|
||||
import { useStore as useAnnouncementStore } from "./stores/announcement-store";
|
||||
import { resetNotices, scheduleBackups } from "./common/notices";
|
||||
@@ -51,7 +50,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
const isFocusMode = useStore((store) => store.isFocusMode);
|
||||
const initUser = useUserStore((store) => store.init);
|
||||
const initStore = useStore((store) => store.init);
|
||||
const initAttachments = useAttachmentStore((store) => store.init);
|
||||
const setIsVaultCreated = useStore((store) => store.setIsVaultCreated);
|
||||
const initEditorStore = useEditorStore((store) => store.init);
|
||||
const dialogAnnouncements = useAnnouncementStore(
|
||||
@@ -77,7 +75,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
);
|
||||
|
||||
initStore();
|
||||
initAttachments();
|
||||
initEditorStore();
|
||||
|
||||
(async function () {
|
||||
@@ -102,7 +99,6 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
[
|
||||
initEditorStore,
|
||||
initStore,
|
||||
initAttachments,
|
||||
updateLastSynced,
|
||||
refreshNavItems,
|
||||
initUser,
|
||||
|
||||
@@ -156,8 +156,7 @@ export function Attachment({
|
||||
<td>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
maxWidth: compact ? 180 : "95%"
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{status ? (
|
||||
@@ -209,7 +208,12 @@ export function Attachment({
|
||||
<Text
|
||||
as="td"
|
||||
variant="body"
|
||||
sx={{ color: status ? "accent" : "paragraph" }}
|
||||
sx={{
|
||||
color: status ? "accent" : "paragraph",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
{status ? (
|
||||
<>
|
||||
|
||||
@@ -281,21 +281,21 @@ function TabStrip() {
|
||||
items={sessions}
|
||||
moveItem={(from, to) => {
|
||||
if (from === to) return;
|
||||
useEditorStore.setState((state) => {
|
||||
const isToPinned = state.sessions[to].pinned;
|
||||
const [fromTab] = state.sessions.splice(from, 1);
|
||||
const sessions = useEditorStore.getState().sessions.slice();
|
||||
const isToPinned = sessions[to].pinned;
|
||||
const [fromTab] = sessions.splice(from, 1);
|
||||
|
||||
// if the tab where this tab is being dropped is pinned,
|
||||
// let's pin our tab too.
|
||||
if (isToPinned) {
|
||||
fromTab.pinned = true;
|
||||
fromTab.preview = false;
|
||||
}
|
||||
// unpin the tab if it is moved.
|
||||
else if (fromTab.pinned) fromTab.pinned = false;
|
||||
// if the tab where this tab is being dropped is pinned,
|
||||
// let's pin our tab too.
|
||||
if (isToPinned) {
|
||||
fromTab.pinned = true;
|
||||
fromTab.preview = false;
|
||||
}
|
||||
// unpin the tab if it is moved.
|
||||
else if (fromTab.pinned) fromTab.pinned = false;
|
||||
|
||||
state.sessions.splice(to, 0, fromTab);
|
||||
});
|
||||
sessions.splice(to, 0, fromTab);
|
||||
useEditorStore.setState({ sessions });
|
||||
}}
|
||||
renderItem={({ item: session, index: i }) => (
|
||||
<Tab
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useEditorStore } from "../../stores/editor-store";
|
||||
import { debounceWithId } from "@notesnook/common";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import useTablet from "../../hooks/use-tablet";
|
||||
import { useEditorConfig } from "./manager";
|
||||
import { useEditorConfig, useEditorManager } from "./manager";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import { replaceDateTime } from "@notesnook/editor/dist/extensions/date-time";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
@@ -131,6 +131,13 @@ function TitleBox(props: TitleBoxProps) {
|
||||
color: "placeholder"
|
||||
}
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
const context = useEditorManager.getState().getEditor(id);
|
||||
if (!context) return;
|
||||
context.editor?.focus({ scrollIntoView: true });
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
pendingChanges.current = true;
|
||||
e.target.value = replaceDateTime(
|
||||
|
||||
@@ -659,6 +659,7 @@ function Attachments({ noteId }: { noteId: string }) {
|
||||
estimatedSize={30}
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.placeholders}
|
||||
style={{ tableLayout: "fixed", width: "100%" }}
|
||||
header={
|
||||
<tr>
|
||||
<th style={{ width: "75%" }} />
|
||||
@@ -666,7 +667,6 @@ function Attachments({ noteId }: { noteId: string }) {
|
||||
<th style={{ width: "20%" }} />
|
||||
</tr>
|
||||
}
|
||||
headerSize={0}
|
||||
renderRow={({ index }) => (
|
||||
<ResolvedItem index={index} type="attachment" items={result.value}>
|
||||
{({ item }) => <ListItemWrapper item={item} compact />}
|
||||
|
||||
@@ -17,9 +17,10 @@ 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 { Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { Box } from "@theme-ui/components";
|
||||
import { Virtualizer } from "@tanstack/react-virtual";
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import React, { useRef } from "react";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
|
||||
export type VirtualizedTableRowProps<T, C> = {
|
||||
item: T;
|
||||
@@ -36,12 +37,10 @@ type VirtualizedTableProps<T, C> = {
|
||||
mode?: "fixed" | "dynamic";
|
||||
items: T[];
|
||||
estimatedSize: number;
|
||||
headerSize: number;
|
||||
getItemKey: (index: number) => string;
|
||||
scrollElement?: Element | null;
|
||||
scrollElement?: HTMLElement | null;
|
||||
context?: C;
|
||||
renderRow: (props: VirtualizedTableRowProps<T, C>) => JSX.Element | null;
|
||||
scrollMargin?: number;
|
||||
header: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
@@ -50,59 +49,95 @@ export function VirtualizedTable<T, C>(props: VirtualizedTableProps<T, C>) {
|
||||
items,
|
||||
getItemKey,
|
||||
scrollElement,
|
||||
scrollMargin,
|
||||
headerSize,
|
||||
renderRow: Row,
|
||||
estimatedSize,
|
||||
mode,
|
||||
virtualizerRef,
|
||||
header,
|
||||
style,
|
||||
context
|
||||
context,
|
||||
style
|
||||
} = props;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: items.length,
|
||||
estimateSize: () => estimatedSize,
|
||||
getItemKey,
|
||||
getScrollElement: () =>
|
||||
scrollElement || containerRef.current?.closest(".ms-container") || null,
|
||||
scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0
|
||||
});
|
||||
|
||||
if (virtualizerRef) virtualizerRef.current = virtualizer;
|
||||
|
||||
const virtualItems = virtualizer.getVirtualItems();
|
||||
return (
|
||||
<Box
|
||||
<Flex
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
height: virtualizer.getTotalSize() + headerSize
|
||||
}}
|
||||
variant="columnFill"
|
||||
sx={{ height: estimatedSize * items.length }}
|
||||
>
|
||||
<table style={style}>
|
||||
<thead>{header}</thead>
|
||||
<tbody>
|
||||
{virtualItems.map((row, index) => (
|
||||
<Row
|
||||
key={row.key}
|
||||
item={items[row.index]}
|
||||
index={row.index}
|
||||
rowRef={mode === "dynamic" ? virtualizer.measureElement : null}
|
||||
context={context}
|
||||
style={{
|
||||
height: mode === "dynamic" ? "unset" : `${row.size}px`,
|
||||
transform: `translateY(${
|
||||
row.start -
|
||||
index * row.size -
|
||||
virtualizer.options.scrollMargin
|
||||
}px)`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Box>
|
||||
<TableVirtuoso
|
||||
data={items}
|
||||
context={context}
|
||||
customScrollParent={
|
||||
scrollElement ||
|
||||
containerRef.current?.closest(".ms-container") ||
|
||||
undefined
|
||||
}
|
||||
computeItemKey={(index) => getItemKey(index)}
|
||||
defaultItemHeight={estimatedSize}
|
||||
fixedHeaderContent={() => <>{header}</>}
|
||||
fixedItemHeight={mode === "fixed" ? estimatedSize : undefined}
|
||||
components={{
|
||||
Table: (props) => (
|
||||
<table {...props} style={{ ...style, ...props.style }} />
|
||||
),
|
||||
TableRow: (props) => {
|
||||
return (
|
||||
<Row
|
||||
index={props["data-item-index"]}
|
||||
item={props.item}
|
||||
style={props.style || {}}
|
||||
context={props.context}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
//
|
||||
|
||||
// const virtualizer = useVirtualizer({
|
||||
// count: items.length,
|
||||
// estimateSize: () => estimatedSize,
|
||||
// getItemKey,
|
||||
// getScrollElement: () =>
|
||||
// scrollElement || containerRef.current?.closest(".ms-container") || null,
|
||||
// scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0
|
||||
// });
|
||||
|
||||
// if (virtualizerRef) virtualizerRef.current = virtualizer;
|
||||
|
||||
// const virtualItems = virtualizer.getVirtualItems();
|
||||
// return (
|
||||
// <Box
|
||||
// ref={containerRef}
|
||||
// sx={{
|
||||
// height: virtualizer.getTotalSize() + headerSize
|
||||
// }}
|
||||
// >
|
||||
// <table style={style}>
|
||||
// <thead>{header}</thead>
|
||||
// <tbody>
|
||||
// {virtualItems.map((row, index) => (
|
||||
// <Row
|
||||
// key={row.key}
|
||||
// item={items[row.index]}
|
||||
// index={row.index}
|
||||
// rowRef={mode === "dynamic" ? virtualizer.measureElement : null}
|
||||
// context={context}
|
||||
// style={{
|
||||
// height: mode === "dynamic" ? "unset" : `${row.size}px`,
|
||||
// transform: `translateY(${
|
||||
// row.start -
|
||||
// index * row.size -
|
||||
// virtualizer.options.scrollMargin
|
||||
// }px)`
|
||||
// }}
|
||||
// />
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </Box>
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useState, memo, useRef } from "react";
|
||||
import { useEffect, useState, memo, useRef, startTransition } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
Text
|
||||
} from "@theme-ui/components";
|
||||
import { store, useStore } from "../stores/attachment-store";
|
||||
import { ResolvedItem, formatBytes, usePromise } from "@notesnook/common";
|
||||
import { formatBytes, usePromise, useResolvedItem } from "@notesnook/common";
|
||||
import Dialog from "../components/dialog";
|
||||
import {
|
||||
ChevronDown,
|
||||
@@ -66,6 +66,7 @@ import { FlexScrollContainer } from "../components/scroll-container";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { ConfirmDialog } from "./confirm";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { Loader } from "../components/loader";
|
||||
|
||||
type ToolbarAction = {
|
||||
title: string;
|
||||
@@ -107,7 +108,7 @@ type SortOptions = {
|
||||
type AttachmentsDialogProps = BaseDialogProps<false>;
|
||||
export const AttachmentsDialog = DialogManager.register(
|
||||
function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
const allAttachments = useStore((store) => store.attachments);
|
||||
const nonce = useStore((store) => store.nonce);
|
||||
const [attachments, setAttachments] =
|
||||
useState<VirtualizedGrouping<AttachmentType>>();
|
||||
const [counts, setCounts] = useState<Record<Route, number>>({
|
||||
@@ -125,29 +126,20 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
direction: "asc"
|
||||
});
|
||||
const currentRoute = useRef<Route>("all");
|
||||
const refresh = useStore((store) => store.refresh);
|
||||
const download = useStore((store) => store.download);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
filterAttachments(currentRoute.current)
|
||||
.sorted({
|
||||
sortBy: sortBy.id,
|
||||
sortDirection: sortBy.direction
|
||||
})
|
||||
.then((value) => startTransition(() => setAttachments(value)));
|
||||
}, [sortBy, nonce]);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
setAttachments(
|
||||
await filterAttachments(currentRoute.current).sorted({
|
||||
sortBy: sortBy.id,
|
||||
sortDirection: sortBy.direction
|
||||
})
|
||||
);
|
||||
})();
|
||||
}, [sortBy, allAttachments]);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
setCounts(await getCounts());
|
||||
})();
|
||||
}, [allAttachments]);
|
||||
getCounts().then((counts) => startTransition(() => setCounts(counts)));
|
||||
}, [nonce]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -234,7 +226,7 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
</Text>
|
||||
</Button> */}
|
||||
</Flex>
|
||||
{attachments && (
|
||||
{attachments ? (
|
||||
<VirtualizedTable
|
||||
style={{
|
||||
tableLayout: "fixed",
|
||||
@@ -327,7 +319,6 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
}
|
||||
mode="fixed"
|
||||
estimatedSize={30}
|
||||
headerSize={40}
|
||||
getItemKey={(index) => attachments.key(index)}
|
||||
items={attachments.placeholders}
|
||||
context={{
|
||||
@@ -345,6 +336,8 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
}}
|
||||
renderRow={AttachmentRow}
|
||||
/>
|
||||
) : (
|
||||
<Loader title="Loading attachments..." />
|
||||
)}
|
||||
</FlexScrollContainer>
|
||||
</Flex>
|
||||
@@ -364,23 +357,21 @@ function AttachmentRow(
|
||||
}
|
||||
>
|
||||
) {
|
||||
if (!props.context) return null;
|
||||
const item = useResolvedItem({
|
||||
index: props.index,
|
||||
items: props.context!.attachments,
|
||||
type: "attachment"
|
||||
});
|
||||
if (!item) return null;
|
||||
return (
|
||||
<ResolvedItem
|
||||
index={props.index}
|
||||
items={props.context.attachments}
|
||||
type="attachment"
|
||||
>
|
||||
{({ item }) => (
|
||||
<Attachment
|
||||
rowRef={props.rowRef}
|
||||
style={props.style}
|
||||
item={item}
|
||||
isSelected={props.context?.isSelected(item.id)}
|
||||
onSelected={() => props.context?.select(item.id)}
|
||||
/>
|
||||
)}
|
||||
</ResolvedItem>
|
||||
<Attachment
|
||||
key={item.item.id}
|
||||
rowRef={props.rowRef}
|
||||
style={props.style}
|
||||
item={item?.item}
|
||||
isSelected={props.context?.isSelected(item.item.id)}
|
||||
onSelected={() => props.context?.select(item.item.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ import { showToast } from "../utils/toast";
|
||||
import { AttachmentStream } from "../utils/streams/attachment-stream";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { createWriteStream } from "../utils/stream-saver";
|
||||
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Attachment } from "@notesnook/core";
|
||||
|
||||
let abortController: AbortController | undefined = undefined;
|
||||
class AttachmentStore extends BaseStore<AttachmentStore> {
|
||||
attachments?: VirtualizedGrouping<Attachment>;
|
||||
nonce = 0;
|
||||
status?: { current: number; total: number };
|
||||
processing: Record<
|
||||
string,
|
||||
@@ -40,17 +40,10 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
|
||||
|
||||
refresh = async () => {
|
||||
this.set({
|
||||
attachments: await db.attachments.all.sorted({
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "desc"
|
||||
})
|
||||
nonce: this.get().nonce + 1
|
||||
});
|
||||
};
|
||||
|
||||
init = () => {
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
download = async (ids: string[]) => {
|
||||
if (this.get().status)
|
||||
throw new Error(
|
||||
|
||||
@@ -142,12 +142,12 @@ function reminderToCronExpression(reminder: Reminder) {
|
||||
const dateTime = dayjs(date);
|
||||
|
||||
if (mode === "once" || !selectedDays) {
|
||||
return dateTime.format("ss mm HH DD MM * YYYY");
|
||||
return dateTime.format("00 mm HH DD MM * YYYY");
|
||||
} else {
|
||||
const cron = dateTime.format("ss mm HH").split(" ");
|
||||
const cron = dateTime.format("00 mm HH").split(" ");
|
||||
if (recurringMode === "year") {
|
||||
cron.push(`${dateTime.date()}`); // day of month
|
||||
cron.push(`${dateTime.month()}`); // month
|
||||
cron.push(`${dateTime.month() + 1}`); // month
|
||||
cron.push("*"); // day of week
|
||||
cron.push("*"); // year
|
||||
} else if (recurringMode === "week") {
|
||||
|
||||
@@ -22,7 +22,7 @@ export function formatBytes(bytes: number, decimals = 2) {
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||
|
||||
4
packages/editor-mobile/package-lock.json
generated
4
packages/editor-mobile/package-lock.json
generated
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"../editor": {
|
||||
"name": "@notesnook/editor",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -123,7 +123,7 @@
|
||||
},
|
||||
"../theme": {
|
||||
"name": "@notesnook/theme",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.7",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@emotion/react": "11.11.1",
|
||||
|
||||
Reference in New Issue
Block a user