Compare commits

...

12 Commits

Author SHA1 Message Date
Ammar Ahmed
a66bc9a8e2 mobile: fix backup share button not working 2024-07-29 09:52:28 +05:00
Abdullah Atta
2c729f90cc global: update lockfiles 2024-07-27 13:30:25 +05:00
Ammar Ahmed
800514d769 mobile: fix position of password input 2024-07-27 12:49:57 +05:00
Ammar Ahmed
b03b9482dc mobile: app stuck after unlock 2024-07-27 12:49:57 +05:00
Abdullah Atta
9019490dbd web: bump version to v3.0.13 2024-07-27 11:16:42 +05:00
Abdullah Atta
4a3e23c288 web: fix reminder can't be set in january 2024-07-27 11:16:09 +05:00
Ammar Ahmed
1800ebea2d mobile: fix keyboard blocks continue button on applock 2024-07-27 11:12:09 +05:00
Ammar Ahmed
a708c9e9eb mobile: fix home shortcut not working 2024-07-27 10:56:09 +05:00
Abdullah Atta
2aeb875fec web: fix crash on moving temporary tab 2024-07-27 10:08:38 +05:00
Abdullah Atta
a8164e9f6c web: enter in title should focus editor 2024-07-27 09:46:37 +05:00
Abdullah Atta
0ee8e738e5 web: fix attachments ui in properties 2024-07-27 09:46:23 +05:00
Abdullah Atta
8fdef2ac7d web: fix attachments flicker in editor note properties 2024-07-27 09:46:23 +05:00
26 changed files with 333 additions and 306 deletions

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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

View File

@@ -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 };

View File

@@ -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;
};

View File

@@ -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]);
};

View File

@@ -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 {

View File

@@ -141,7 +141,6 @@ const onBoot = async () => {
}
await Notifications.setupReminders();
SettingsService.init();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}

View File

@@ -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);
}

View File

@@ -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) => {

View File

@@ -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"

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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": {

View File

@@ -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/",

View File

@@ -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,

View File

@@ -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 ? (
<>

View File

@@ -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

View File

@@ -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(

View File

@@ -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 />}

View File

@@ -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>
// );
}

View File

@@ -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)}
/>
);
}

View File

@@ -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(

View File

@@ -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") {

View File

@@ -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];

View File

@@ -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",