mobile: ui redesign

This commit is contained in:
Ammar Ahmed
2024-12-06 10:27:42 +05:00
committed by Abdullah Atta
parent 8f84c85c23
commit b315a834c2
81 changed files with 4362 additions and 3027 deletions

View File

@@ -19,20 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { i18n } from "@lingui/core"; import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react"; import { I18nProvider } from "@lingui/react";
import { import {
ScopedThemeProvider,
THEME_COMPATIBILITY_VERSION, THEME_COMPATIBILITY_VERSION,
useThemeEngineStore useThemeEngineStore
} from "@notesnook/theme"; } from "@notesnook/theme";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { I18nManager, View } from "react-native"; import { I18nManager, StatusBar } from "react-native";
import "react-native-gesture-handler"; import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler"; import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context"; import { SafeAreaProvider } from "react-native-safe-area-context";
import AppLockedOverlay from "./components/app-lock-overlay"; import DialogProvider from "./components/dialog-provider";
import { withErrorBoundry } from "./components/exception-handler"; import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea"; import GlobalSafeAreaProvider from "./components/globalsafearea";
import { Toast } from "./components/toast";
import { useAppEvents } from "./hooks/use-app-events"; import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { NotePreviewConfigure } from "./screens/note-preview-configure"; import { NotePreviewConfigure } from "./screens/note-preview-configure";
import { RootNavigation } from "./navigation/navigation-stack";
import { themeTrpcClient } from "./screens/settings/theme-selector"; import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications"; import Notifications from "./services/notifications";
import SettingsService from "./services/settings"; import SettingsService from "./services/settings";
@@ -64,42 +66,26 @@ const App = (props: { configureMode: "note-preview" }) => {
}, []); }, []);
return ( return (
<View
style={{
height: "100%",
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<View
style={{
position: "absolute",
width: "100%",
height: "100%",
zIndex: -1
}}
pointerEvents="none"
>
<SafeAreaProvider> <SafeAreaProvider>
<GlobalSafeAreaProvider /> <StatusBar translucent={true} backgroundColor="transparent" />
</SafeAreaProvider>
</View>
<GestureHandlerRootView <GestureHandlerRootView
style={{ style={{
height: "100%", height: "100%",
width: "100%" width: "100%"
}} }}
> >
<GlobalSafeAreaProvider />
{props.configureMode === "note-preview" ? ( {props.configureMode === "note-preview" ? (
<NotePreviewConfigure /> <NotePreviewConfigure />
) : ( ) : (
<ApplicationHolder /> <RootNavigation />
)} )}
<ScopedThemeProvider value="dialog">
<Toast />
</ScopedThemeProvider>
<DialogProvider />
</GestureHandlerRootView> </GestureHandlerRootView>
<AppLockedOverlay /> </SafeAreaProvider>
</View>
); );
}; };

View File

@@ -82,7 +82,7 @@ const verifyUserPassword = async (password: string) => {
} }
}; };
const AppLockedOverlay = () => { const AppLockedScreen = () => {
const initialLaunchBiometricRequest = useRef(true); const initialLaunchBiometricRequest = useRef(true);
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const user = getUser(); const user = getUser();
@@ -203,14 +203,12 @@ const AppLockedOverlay = () => {
} }
}, [appState, onUnlockAppRequested, appLocked]); }, [appState, onUnlockAppRequested, appLocked]);
return appLocked ? ( return (
<KeyboardAwareScrollView <KeyboardAwareScrollView
style={{ style={{
backgroundColor: colors.primary.background, backgroundColor: colors.primary.background,
width: "100%", width: "100%",
height: "100%", height: "100%"
position: "absolute",
zIndex: 999
}} }}
contentContainerStyle={{ contentContainerStyle={{
justifyContent: "center", justifyContent: "center",
@@ -333,7 +331,7 @@ const AppLockedOverlay = () => {
</View> </View>
</View> </View>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>
) : null; );
}; };
export default AppLockedOverlay; export default AppLockedScreen;

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import { useRoute } from "@react-navigation/native"; import { useRoute } from "@react-navigation/native";
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { Keyboard, View } from "react-native"; import { Keyboard, TouchableOpacity, View } from "react-native";
import Animated, { import Animated, {
Easing, Easing,
useAnimatedStyle, useAnimatedStyle,
@@ -34,7 +34,7 @@ import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation"; import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size"; import { SIZE, normalize } from "../../utils/size";
import { Pressable } from "../ui/pressable"; import { DefaultAppStyles } from "../../utils/styles";
interface FloatingButtonProps { interface FloatingButtonProps {
onPress: () => void; onPress: () => void;
@@ -108,20 +108,24 @@ const FloatingButton = ({
style={[ style={[
{ {
position: "absolute", position: "absolute",
right: 12, right: DefaultAppStyles.GAP,
bottom: 20, bottom: 20,
zIndex: 10 zIndex: 10
}, },
animatedStyle animatedStyle
]} ]}
> >
<Pressable <TouchableOpacity
testID={notesnook.buttons.add} testID={notesnook.buttons.add}
type="accent" activeOpacity={0.95}
accentColor={color}
style={{ style={{
...getElevationStyle(5), ...getElevationStyle(10),
borderRadius: 100 borderRadius: 20,
borderTopWidth: 0,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 0,
backgroundColor: colors.primary.background
}} }}
onPress={onPress} onPress={onPress}
> >
@@ -130,7 +134,9 @@ const FloatingButton = ({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
height: normalize(60), height: normalize(60),
width: normalize(60) width: normalize(60),
backgroundColor: colors.primary.shade,
borderRadius: 20
}} }}
> >
<Icon <Icon
@@ -141,11 +147,11 @@ const FloatingButton = ({
? "delete" ? "delete"
: "plus" : "plus"
} }
color={colors.primary.accentForeground} color={colors.primary.accent}
size={SIZE.xxl} size={SIZE.xxxl}
/> />
</View> </View>
</Pressable> </TouchableOpacity>
</Animated.View> </Animated.View>
); );
}; };

View File

@@ -46,8 +46,6 @@ const DialogButtons = ({
{ {
backgroundColor: colors.secondary.background, backgroundColor: colors.secondary.background,
height: 60, height: 60,
borderBottomRightRadius: 5,
borderBottomLeftRadius: 5,
paddingHorizontal: 12, paddingHorizontal: 12,
borderTopWidth: 0.7, borderTopWidth: 0.7,
borderTopColor: getColorLinearShade( borderTopColor: getColorLinearShade(

View File

@@ -39,6 +39,7 @@ import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors"; import { getContainerBorder } from "../../utils/colors";
import { Notice } from "../ui/notice"; import { Notice } from "../ui/notice";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { br } from "../../utils/size";
export const Dialog = ({ context = "global" }) => { export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
@@ -125,10 +126,11 @@ export const Dialog = ({ context = "global" }) => {
...getElevationStyle(5), ...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%", width: DDS.isTab ? 400 : "85%",
maxHeight: 450, maxHeight: 450,
borderRadius: 5, borderRadius: br,
backgroundColor: colors.primary.background, backgroundColor: colors.primary.background,
paddingTop: 12, paddingTop: 12,
...getContainerBorder(colors.primary.border, 0.5) ...getContainerBorder(colors.primary.border, 0.5),
overflow: "hidden"
}; };
return visible ? ( return visible ? (

View File

@@ -41,7 +41,7 @@ import {
eUpdateNoteInEditor eUpdateNoteInEditor
} from "../../../utils/events"; } from "../../../utils/events";
import { deleteItems } from "../../../utils/functions"; import { deleteItems } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs"; import { fluidTabsRef } from "../../../utils/global-refs";
import { convertNoteToText } from "../../../utils/note-to-text"; import { convertNoteToText } from "../../../utils/note-to-text";
import { sleep } from "../../../utils/time"; import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog"; import BaseDialog from "../../dialog/base-dialog";
@@ -553,7 +553,7 @@ export class VaultDialog extends Component {
item: note item: note
}); });
if (!DDS.isTab) { if (!DDS.isTab) {
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} }
}); });
} }

View File

@@ -37,12 +37,12 @@ import Animated, {
WithSpringConfig, WithSpringConfig,
withTiming withTiming
} from "react-native-reanimated"; } from "react-native-reanimated";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { getAppState } from "../../screens/editor/tiptap/utils"; import { getAppState } from "../../screens/editor/tiptap/utils";
import { eSendEvent } from "../../services/event-manager"; import { eSendEvent } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { eClearEditor } from "../../utils/events"; import { eClearEditor } from "../../utils/events";
import { useSideBarDraggingStore } from "../side-menu/dragging-store"; import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
interface TabProps extends ViewProps { interface TabProps extends ViewProps {
dimensions: { width: number; height: number }; dimensions: { width: number; height: number };
@@ -66,7 +66,7 @@ export interface TabsRef {
node: RefObject<Animated.View>; node: RefObject<Animated.View>;
} }
export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs( export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
{ {
children, children,
dimensions, dimensions,
@@ -81,9 +81,6 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const appState = useMemo(() => getAppState(), []); const appState = useMemo(() => getAppState(), []);
const deviceMode = useSettingStore((state) => state.deviceMode); const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen); const fullscreen = useSettingStore((state) => state.fullscreen);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const translateX = useSharedValue( const translateX = useSharedValue(
widths widths
? appState && ? appState &&
@@ -118,7 +115,6 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const isIPhone = Platform.OS === "ios"; const isIPhone = Platform.OS === "ios";
useEffect(() => { useEffect(() => {
if (introCompleted) {
if (deviceMode === "tablet" || fullscreen) { if (deviceMode === "tablet" || fullscreen) {
translateX.value = 0; translateX.value = 0;
} else { } else {
@@ -134,10 +130,19 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
} }
isLoaded.current = true; isLoaded.current = true;
prevWidths.current = widths; prevWidths.current = widths;
} }, [
deviceMode,
widths,
fullscreen,
translateX,
editorPosition,
appState,
onChangeTab
]);
useEffect(() => {
const sub = BackHandler.addEventListener("hardwareBackPress", () => { const sub = BackHandler.addEventListener("hardwareBackPress", () => {
if (isDrawerOpen.value) { if (isDrawerOpen.value && !forcedLock.value) {
translateX.value = withTiming(homePosition); translateX.value = withTiming(homePosition);
onDrawerStateChange(false); onDrawerStateChange(false);
isDrawerOpen.value = false; isDrawerOpen.value = false;
@@ -149,16 +154,11 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
sub && sub.remove(); sub && sub.remove();
}; };
}, [ }, [
introCompleted, forcedLock.value,
deviceMode,
widths,
fullscreen,
translateX,
isDrawerOpen,
homePosition, homePosition,
isDrawerOpen,
onDrawerStateChange, onDrawerStateChange,
editorPosition, translateX
appState
]); ]);
useImperativeHandle( useImperativeHandle(
@@ -294,8 +294,8 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const gesture = Gesture.Pan() const gesture = Gesture.Pan()
.maxPointers(1) .maxPointers(1)
.enabled(enabled && !disabled) .enabled(enabled && !disabled)
.activeOffsetX([-5, 5]) .activeOffsetX([-20, 20])
.failOffsetY([-15, 15]) .failOffsetY([-10, 10])
.onBegin((event) => { .onBegin((event) => {
locked.value = false; locked.value = false;
gestureStartValue.value = { gestureStartValue.value = {

View File

@@ -17,60 +17,52 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { Platform, StyleSheet, View } from "react-native"; import { View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { import {
eSubscribeEvent, eSubscribeEvent,
eUnSubscribeEvent eUnSubscribeEvent
} from "../../services/event-manager"; } from "../../services/event-manager";
import useNavigationStore, { import { RouteName } from "../../stores/use-navigation-store";
RouteName
} from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store"; import { useSelectionStore } from "../../stores/use-selection-store";
import { eScrollEvent } from "../../utils/events"; import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { IconButtonProps } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { LeftMenus } from "./left-menus"; import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus"; import { RightMenus } from "./right-menus";
import { Title } from "./title";
type HeaderRightButton = {
title: string;
onPress: () => void;
};
export const Header = ({ export const Header = ({
renderedInRoute, renderedInRoute,
onLeftMenuButtonPress, onLeftMenuButtonPress,
title, title,
titleHiddenOnRender,
headerRightButtons,
id, id,
accentColor,
isBeta,
canGoBack, canGoBack,
onPressDefaultRightButton,
hasSearch, hasSearch,
onSearch onSearch,
rightButton
}: { }: {
onLeftMenuButtonPress?: () => void; onLeftMenuButtonPress?: () => void;
renderedInRoute?: RouteName; renderedInRoute?: RouteName;
id?: string; id?: string;
title: string; title: string;
headerRightButtons?: HeaderRightButton[];
titleHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
canGoBack?: boolean; canGoBack?: boolean;
onPressDefaultRightButton?: () => void; onPressDefaultRightButton?: () => void;
hasSearch?: boolean; hasSearch?: boolean;
onSearch?: () => void; onSearch?: () => void;
rightButton?: IconButtonProps;
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true); const [borderHidden, setBorderHidden] = useState(true);
const selectionMode = useSelectionStore((state) => state.selectionMode); const [selectedItemsList, selectionMode] = useSelectionStore((state) => [
const isFocused = useNavigationStore((state) => state.focusedRouteId === id); state.selectedItemsList,
state.selectionMode
]);
const onScroll = useCallback( const onScroll = useCallback(
(data: { x: number; y: number; id?: string; route: string }) => { (data: { x: number; y: number; id?: string; route: string }) => {
@@ -93,88 +85,47 @@ export const Header = ({
}; };
}, [borderHidden, onScroll]); }, [borderHidden, onScroll]);
return selectionMode && isFocused ? null : ( const HeaderWrapper = hasSearch ? Pressable : View;
<>
return (
<View <View
style={[ style={{
styles.container, paddingHorizontal: DefaultAppStyles.GAP
{ }}
marginTop: Platform.OS === "android" ? insets.top : null, >
backgroundColor: colors.primary.background, <HeaderWrapper
overflow: "hidden", style={{
borderBottomWidth: 1, flexDirection: "row",
borderBottomColor: borderHidden justifyContent: "space-between",
? "transparent" marginTop: DefaultAppStyles.GAP_SMALL,
: colors.secondary.background, borderRadius: 10,
justifyContent: "space-between" paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
} borderWidth: hasSearch ? 1 : 0,
]} borderColor: colors.secondary.border,
paddingHorizontal: !hasSearch ? 0 : DefaultAppStyles.GAP_SMALL,
alignItems: "center"
}}
onPress={() => {
onSearch?.();
}}
> >
<>
<View style={styles.leftBtnContainer}>
<LeftMenus <LeftMenus
canGoBack={canGoBack} canGoBack={canGoBack}
onLeftButtonPress={onLeftMenuButtonPress} onLeftButtonPress={onLeftMenuButtonPress}
/> />
<Title {hasSearch ? (
isHiddenOnRender={titleHiddenOnRender} <Paragraph>
renderedInRoute={renderedInRoute} {selectionMode
id={id} ? `${selectedItemsList.length} selected`
accentColor={accentColor} : strings.searchInRoute(title)}
title={title} </Paragraph>
isBeta={isBeta} ) : (
/> <Heading size={SIZE.lg}>{title}</Heading>
)}
<RightMenus rightButton={rightButton} />
</HeaderWrapper>
</View> </View>
<RightMenus
renderedInRoute={renderedInRoute}
id={id}
headerRightButtons={headerRightButtons}
onPressDefaultRightButton={onPressDefaultRightButton}
search={hasSearch}
onSearch={onSearch}
/>
</>
</View>
</>
); );
}; };
const styles = StyleSheet.create({
container: {
flexDirection: "row",
zIndex: 11,
height: 50,
maxHeight: 50,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 12,
width: "100%"
},
leftBtnContainer: {
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
flexShrink: 1
},
leftBtn: {
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
marginLeft: -5,
marginRight: 25
},
rightBtnContainer: {
flexDirection: "row",
alignItems: "center"
},
rightBtn: {
justifyContent: "center",
alignItems: "flex-end",
height: 40,
width: 40,
paddingRight: 0
}
});

View File

@@ -20,10 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { notesnook } from "../../../e2e/test.ids"; import { notesnook } from "../../../e2e/test.ids";
import { DDS } from "../../services/device-detection";
import Navigation from "../../services/navigation"; import Navigation from "../../services/navigation";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
export const LeftMenus = ({ export const LeftMenus = ({
@@ -41,7 +40,7 @@ export const LeftMenus = ({
if (onLeftButtonPress) return onLeftButtonPress(); if (onLeftButtonPress) return onLeftButtonPress();
if (!canGoBack) { if (!canGoBack) {
if (tabBarRef.current?.isDrawerOpen()) { if (fluidTabsRef.current?.isDrawerOpen()) {
Navigation.closeDrawer(); Navigation.closeDrawer();
} else { } else {
Navigation.openDrawer(); Navigation.openDrawer();
@@ -54,23 +53,14 @@ export const LeftMenus = ({
return isTablet && !canGoBack ? null : ( return isTablet && !canGoBack ? null : (
<IconButton <IconButton
testID={notesnook.ids.default.header.buttons.left} testID={notesnook.ids.default.header.buttons.left}
style={{
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
marginRight: DDS.isLargeTablet() ? 10 : 7
}}
left={40} left={40}
top={40} top={40}
right={DDS.isLargeTablet() ? 10 : 10}
onPress={_onLeftButtonPress} onPress={_onLeftButtonPress}
onLongPress={() => { onLongPress={() => {
Navigation.popToTop(); Navigation.popToTop();
}} }}
name={canGoBack ? "arrow-left" : "menu"} name={canGoBack ? "arrow-left" : "menu"}
color={colors.primary.paragraph} color={colors.primary.icon}
/> />
); );
}; };

View File

@@ -17,127 +17,30 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import { Menu } from "react-native-material-menu"; import React from "react";
import { notesnook } from "../../../e2e/test.ids"; import { StyleSheet, View } from "react-native";
import { import { IconButton, IconButtonProps } from "../ui/icon-button";
HeaderRightButton,
RouteName
} from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
export const RightMenus = ({ export const RightMenus = ({
headerRightButtons, rightButton
renderedInRoute,
id,
onPressDefaultRightButton,
search,
onSearch
}: { }: {
headerRightButtons?: HeaderRightButton[]; rightButton?: IconButtonProps;
renderedInRoute?: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;
onSearch?: () => void;
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const deviceMode = useSettingStore((state) => state.deviceMode);
const menuRef = useRef<Menu>(null);
return ( return (
<View style={styles.rightBtnContainer}> <View style={styles.rightBtnContainer}>
{search ? ( {rightButton ? (
<IconButton <IconButton {...rightButton} color={colors.primary.icon} />
onPress={onSearch} ) : (
testID="icon-search" <View
name="magnify"
color={colors.primary.paragraph}
style={styles.rightBtn}
/>
) : null}
{deviceMode !== "mobile" ? (
<Button
onPress={onPressDefaultRightButton}
testID={notesnook.ids.default.addBtn}
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
iconSize={SIZE.xl}
type="accent"
hitSlop={{
top: 10,
right: 10,
bottom: 10,
left: 0
}}
style={{ style={{
marginLeft: 10, width: 40,
width: 32, height: 40
height: 32,
borderRadius: 100,
paddingHorizontal: 0,
borderWidth: 1,
borderColor: colors.primary.accent
}} }}
/> />
) : null} )}
{headerRightButtons && headerRightButtons.length > 0 ? (
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
color={colors.primary.paragraph}
style={styles.rightBtn}
/>
}
>
{headerRightButtons.map((item) => (
<Button
style={{
justifyContent: "flex-start",
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}
key={item.title}
title={item.title}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();
}}
/>
))}
</Menu>
) : null}
</View> </View>
); );
}; };
@@ -149,10 +52,6 @@ const styles = StyleSheet.create({
}, },
rightBtn: { rightBtn: {
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center"
height: 40,
width: 40,
marginLeft: 10,
paddingRight: 0
} }
}); });

View File

@@ -17,122 +17,43 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { getFormattedDate } from "@notesnook/common";
import { Notebook } from "@notesnook/core"; import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import { db } from "../../../common/database";
import { ToastManager } from "../../../services/event-manager";
import { useMenuStore } from "../../../stores/use-menu-store";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button"; import { DefaultAppStyles } from "../../../utils/styles";
import AppIcon from "../../ui/AppIcon";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
export const NotebookHeader = ({ export const NotebookHeader = ({
notebook, notebook,
onEditNotebook,
totalNotes = 0 totalNotes = 0
}: { }: {
notebook: Notebook; notebook: Notebook;
onEditNotebook: () => void;
totalNotes: number; totalNotes: number;
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(notebook.id)
);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const onPinNotebook = async () => {
try {
if (isPinnedToMenu) {
await db.shortcuts.remove(notebook.id);
} else {
await db.shortcuts.add({
item: {
id: notebook.id,
type: "notebook"
}
});
ToastManager.show({
heading: strings.shortcutCreated(),
type: "success"
});
}
setIsPinnedToMenu(db.shortcuts.exists(notebook.id));
setMenuPins();
} catch (e) {
console.error(e);
}
};
return ( return (
<View <View
style={{ style={{
marginBottom: 5, paddingHorizontal: DefaultAppStyles.GAP,
padding: 0, marginVertical: DefaultAppStyles.GAP,
backgroundColor: colors.secondary.background
}}
>
<View
style={{
width: "100%", width: "100%",
paddingVertical: 15, gap: DefaultAppStyles.GAP_VERTICAL,
paddingHorizontal: 12, paddingVertical: 25
alignSelf: "center",
borderRadius: 10,
paddingTop: 25
}} }}
> >
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}> <AppIcon name="notebook" size={SIZE.xxl} />
{getFormattedDate(notebook.dateModified, "date-time")} <Heading size={SIZE.lg}>{notebook.title}</Heading>
</Paragraph>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading
style={{
flexShrink: 1
}}
size={SIZE.lg}
>
{notebook.title}
</Heading>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
onPress={onPinNotebook}
tooltipText={"Create shortcut in side menu"}
style={{
marginRight: 15,
width: 40,
height: 40
}}
type="transparent"
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
<IconButton
size={SIZE.lg}
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="transparent"
color={colors.primary.icon}
style={{
width: 40,
height: 40
}}
/>
</View>
</View>
{notebook.description ? ( {notebook.description ? (
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}> <Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
@@ -140,17 +61,21 @@ export const NotebookHeader = ({
</Paragraph> </Paragraph>
) : null} ) : null}
<Paragraph <View
style={{ style={{
marginTop: 10, flexDirection: "row",
fontStyle: "italic", gap: DefaultAppStyles.GAP_SMALL
fontFamily: undefined
}} }}
size={SIZE.xs}
color={colors.secondary.paragraph}
> >
<Paragraph size={SIZE.xxs} color={colors.secondary.paragraph}>
{strings.notes(totalNotes || 0)} {strings.notes(totalNotes || 0)}
</Paragraph> </Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxs}>
{getFormattedDate(notebook.dateModified, "date-time")}
</Paragraph>
</View>
</View>
</View> </View>
); );
}; };

View File

@@ -21,16 +21,16 @@ import { GroupHeader, GroupOptions, ItemType } from "@notesnook/core";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native"; import { useWindowDimensions, View } from "react-native";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled"; import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { presentSheet } from "../../../services/event-manager"; import { presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings"; import SettingsService from "../../../services/settings";
import { RouteName } from "../../../stores/use-navigation-store"; import { RouteName } from "../../../stores/use-navigation-store";
import { getContainerBorder } from "../../../utils/colors";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import Sort from "../../sheets/sort"; import Sort from "../../sheets/sort";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button"; import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
type SectionHeaderProps = { type SectionHeaderProps = {
@@ -65,91 +65,88 @@ export const SectionHeader = React.memo<
return ( return (
<View <View
style={{ style={{
flexDirection: "row", width: "100%",
alignItems: "center", paddingHorizontal: DefaultAppStyles.GAP,
width: "95%", marginVertical: DefaultAppStyles.GAP
justifyContent: "space-between",
paddingHorizontal: 12,
height: 35 * fontScale,
backgroundColor: colors.secondary.background,
alignSelf: "center",
borderRadius: 5,
marginVertical: 5,
...getContainerBorder(colors.secondary.background, 0.8)
}} }}
> >
<TouchableOpacity <View
style={{
flexDirection: "row",
alignItems: "center",
width: "100%",
alignSelf: "center",
justifyContent: "space-between",
borderBottomWidth: 1,
borderColor: colors.primary.border,
paddingBottom: DefaultAppStyles.GAP_VERTICAL
}}
>
<Pressable
onPress={() => { onPress={() => {
onOpenJumpToDialog(); onOpenJumpToDialog();
}} }}
activeOpacity={0.9}
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }} hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
style={{ style={{
height: "100%", justifyContent: "flex-start",
justifyContent: "center" flexDirection: "row",
width: "auto"
}} }}
> >
<Heading <Heading
color={color || colors.primary.accent}
size={SIZE.sm} size={SIZE.sm}
style={{ style={{
alignSelf: "center", alignSelf: "center",
textAlignVertical: "center" textAlignVertical: "center"
}} }}
color={colors.primary.accent}
> >
{!item.title || item.title === "" ? strings.pinned() : item.title} {!item.title || item.title === "" ? strings.pinned() : item.title}
</Heading> </Heading>
</TouchableOpacity> </Pressable>
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center" alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL
}} }}
> >
{index === 0 ? ( {index === 0 ? (
<> <>
<Button <IconButton
onPress={() => { name={
presentSheet({
component: <Sort screen={screen} type={dataType} />
});
}}
title={groupBy}
icon={
groupOptions.sortDirection === "asc" groupOptions.sortDirection === "asc"
? "sort-ascending" ? "sort-ascending"
: "sort-descending" : "sort-descending"
} }
height={25} onPress={() => {
style={{ if (!screen) return;
borderRadius: 100, presentSheet({
paddingHorizontal: 0, component: <Sort screen={screen} type={dataType} />
backgroundColor: "transparent", });
marginRight:
dataType === "note" ||
screen === "Notes" ||
dataType === "notebook"
? 10
: 0
}} }}
type="plain"
iconPosition="right"
/>
<IconButton
style={{ style={{
width: 25, width: 25,
height: 25 height: 25
}} }}
size={SIZE.lg - 2}
/>
<IconButton
hidden={ hidden={
dataType !== "note" && dataType !== "note" &&
dataType !== "notebook" && dataType !== "notebook" &&
screen !== "Notes" screen !== "Notes"
} }
style={{
width: 25,
height: 25
}}
testID="icon-compact-mode" testID="icon-compact-mode"
color={colors.secondary.icon} color={colors.secondary.icon}
name={isCompactModeEnabled ? "view-list" : "view-list-outline"} name={
isCompactModeEnabled ? "view-list" : "view-list-outline"
}
onPress={() => { onPress={() => {
SettingsService.set({ SettingsService.set({
[dataType !== "notebook" [dataType !== "notebook"
@@ -163,6 +160,16 @@ export const SectionHeader = React.memo<
/> />
</> </>
) : null} ) : null}
<IconButton
style={{
width: 25,
height: 25
}}
name={"chevron-down"}
size={SIZE.lg - 2}
/>
</View>
</View> </View>
</View> </View>
); );

View File

@@ -29,24 +29,25 @@ import { EntityLevel, decode } from "entities";
import React from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled"; import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import NotebookScreen from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import useNavigationStore from "../../../stores/use-navigation-store"; import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common"; import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { notesnook } from "../../../../e2e/test.ids";
import useIsSelected from "../../../hooks/use-selected";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { DefaultAppStyles } from "../../../utils/styles";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Button } from "../../ui/button"; import AppIcon from "../../ui/AppIcon";
import { IconButton } from "../../ui/icon-button"; import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time"; import { ReminderTime } from "../../ui/reminder-time";
import { TimeSince } from "../../ui/time-since"; import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type NoteItemProps = { type NoteItemProps = {
item: Note | BaseTrashItem<Note>; item: Note | BaseTrashItem<Note>;
@@ -85,6 +86,8 @@ const NoteItem = ({
); );
const _update = useRelationStore((state) => state.updater); const _update = useRelationStore((state) => state.updater);
const primaryColors = isEditingNote ? colors.selected : colors.primary; const primaryColors = isEditingNote ? colors.selected : colors.primary;
const selectionMode = useSelectionStore((state) => state.selectionMode);
const [selected] = useIsSelected(item);
return ( return (
<> <>
@@ -94,64 +97,6 @@ const NoteItem = ({
flexShrink: 1 flexShrink: 1
}} }}
> >
{!compactMode ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
zIndex: 10,
elevation: 10,
marginBottom: 2.5,
flexWrap: "wrap"
}}
>
{notebooks?.items
?.filter(
(item) =>
item.id !== useNavigationStore.getState().focusedRouteId
)
.map((item) => (
<Button
title={
item.title.length > 25
? item.title.slice(0, 25) + "..."
: item.title
}
tooltipText={item.title}
key={item.id}
height={25}
icon="book-outline"
type="secondary"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
textStyle={{
marginRight: 0
}}
style={{
borderRadius: 5,
marginRight: 5,
paddingHorizontal: 6,
marginBottom: 5
}}
onPress={() => {
NotebookScreen.navigate(item, true);
}}
/>
))}
<ReminderTime
reminder={reminder}
color={color?.colorCode}
onPress={() => {
Properties.present(reminder);
}}
style={{
height: 25
}}
/>
</View>
) : null}
{compactMode ? ( {compactMode ? (
<Paragraph <Paragraph
numberOfLines={1} numberOfLines={1}
@@ -167,7 +112,7 @@ const NoteItem = ({
<Heading <Heading
numberOfLines={1} numberOfLines={1}
color={color?.colorCode || primaryColors.heading} color={color?.colorCode || primaryColors.heading}
size={SIZE.md} size={SIZE.sm}
style={{ style={{
paddingRight: 10 paddingRight: 10
}} }}
@@ -198,7 +143,9 @@ const NoteItem = ({
alignItems: "center", alignItems: "center",
width: "100%", width: "100%",
marginTop: 5, marginTop: 5,
height: SIZE.md + 2 columnGap: 8,
rowGap: 4,
flexWrap: "wrap"
}} }}
> >
{!isTrash ? ( {!isTrash ? (
@@ -206,9 +153,6 @@ const NoteItem = ({
{item.conflicted ? ( {item.conflicted ? (
<Icon <Icon
name="alert-circle" name="alert-circle"
style={{
marginRight: 6
}}
size={SIZE.sm} size={SIZE.sm}
color={colors.error.accent} color={colors.error.accent}
/> />
@@ -219,9 +163,6 @@ const NoteItem = ({
testID="sync-off" testID="sync-off"
name="sync-off" name="sync-off"
size={SIZE.sm} size={SIZE.sm}
style={{
marginRight: 6
}}
color={primaryColors.icon} color={primaryColors.icon}
/> />
) : null} ) : null}
@@ -231,18 +172,14 @@ const NoteItem = ({
testID="pencil-lock" testID="pencil-lock"
name="pencil-lock" name="pencil-lock"
size={SIZE.sm} size={SIZE.sm}
style={{
marginRight: 6
}}
color={primaryColors.icon} color={primaryColors.icon}
/> />
) : null} ) : null}
<TimeSince <TimeSince
style={{ style={{
fontSize: SIZE.xs, fontSize: SIZE.xxs,
color: colors.secondary.paragraph, color: colors.secondary.paragraph
marginRight: 6
}} }}
time={date} time={date}
updateFrequency={Date.now() - date < 60000 ? 2000 : 60000} updateFrequency={Date.now() - date < 60000 ? 2000 : 60000}
@@ -253,7 +190,6 @@ const NoteItem = ({
style={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
marginRight: 6,
gap: 2 gap: 2
}} }}
> >
@@ -264,7 +200,7 @@ const NoteItem = ({
/> />
<Paragraph <Paragraph
color={colors.secondary.paragraph} color={colors.secondary.paragraph}
size={SIZE.xs} size={SIZE.xxs}
> >
{attachmentsCount} {attachmentsCount}
</Paragraph> </Paragraph>
@@ -275,10 +211,7 @@ const NoteItem = ({
<Icon <Icon
testID="icon-pinned" testID="icon-pinned"
name="pin-outline" name="pin-outline"
size={SIZE.sm} size={SIZE.xs}
style={{
marginRight: 6
}}
color={color?.colorCode || primaryColors.accent} color={color?.colorCode || primaryColors.accent}
/> />
) : null} ) : null}
@@ -288,9 +221,6 @@ const NoteItem = ({
name="lock" name="lock"
testID="note-locked-icon" testID="note-locked-icon"
size={SIZE.sm} size={SIZE.sm}
style={{
marginRight: 6
}}
color={primaryColors.icon} color={primaryColors.icon}
/> />
) : null} ) : null}
@@ -300,36 +230,80 @@ const NoteItem = ({
testID="icon-star" testID="icon-star"
name="star-outline" name="star-outline"
size={SIZE.sm} size={SIZE.sm}
style={{
marginRight: 6
}}
color="orange" color="orange"
/> />
) : null} ) : null}
{reminder ? (
<ReminderTime
reminder={reminder}
disabled
color={color?.colorCode}
textStyle={{
fontSize: SIZE.xxxs
}}
iconSize={SIZE.xxxs}
style={{
height: "auto"
}}
/>
) : null}
{notebooks?.items
?.filter(
(item) =>
item.id !== useNavigationStore.getState().focusedRouteId
)
.map((item) => (
<View
key={item.id}
style={{
borderRadius: 4,
backgroundColor: colors.secondary.background,
paddingHorizontal: 4,
borderWidth: 0.5,
borderColor: primaryColors.border,
paddingVertical: 1,
flexDirection: "row",
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL / 2
}}
>
<AppIcon
name="book-outline"
size={SIZE.xxxs}
color={colors.secondary.icon}
/>
<Paragraph
size={SIZE.xxxs}
color={colors.secondary.paragraph}
>
{item.title}
</Paragraph>
</View>
))}
{!isTrash && !compactMode && tags {!isTrash && !compactMode && tags
? tags.items?.map((item) => ? tags.items?.map((item) =>
item.id ? ( item.id ? (
<Button <View
title={"#" + item.title}
key={item.id} key={item.id}
height={23}
type="plain"
textStyle={{
textDecorationLine: "underline",
color: colors.secondary.paragraph
}}
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
fontSize={SIZE.xs}
style={{ style={{
borderRadius: 5, borderRadius: 4,
paddingHorizontal: 6, backgroundColor: colors.secondary.background,
marginRight: 4, paddingHorizontal: 4,
zIndex: 10, borderWidth: 0.5,
maxWidth: tags.items?.length > 1 ? 130 : null borderColor: primaryColors.border,
paddingVertical: 1
}} }}
onPress={() => TaggedNotes.navigate(item, true)} >
/> <Paragraph
size={SIZE.xxxs}
color={colors.secondary.paragraph}
>
#{item.title}
</Paragraph>
</View>
) : null ) : null
) )
: null} : null}
@@ -338,7 +312,7 @@ const NoteItem = ({
<> <>
<Paragraph <Paragraph
color={colors.secondary.paragraph} color={colors.secondary.paragraph}
size={SIZE.xs} size={SIZE.xxs}
style={{ style={{
marginRight: 6 marginRight: 6
}} }}
@@ -352,7 +326,7 @@ const NoteItem = ({
<Paragraph <Paragraph
color={primaryColors.accent} color={primaryColors.accent}
size={SIZE.xs} size={SIZE.xxs}
style={{ style={{
marginRight: 6 marginRight: 6
}} }}
@@ -409,7 +383,7 @@ const NoteItem = ({
<TimeSince <TimeSince
style={{ style={{
fontSize: SIZE.xs, fontSize: SIZE.xxs,
color: colors.secondary.paragraph, color: colors.secondary.paragraph,
marginRight: 6 marginRight: 6
}} }}
@@ -419,11 +393,20 @@ const NoteItem = ({
</> </>
) : null} ) : null}
{selectionMode ? (
<>
<AppIcon
name={selected ? "checkbox-outline" : "checkbox-blank-outline"}
color={selected ? colors.selected.icon : colors.primary.icon}
size={SIZE.lg}
/>
</>
) : (
<IconButton <IconButton
testID={notesnook.listitem.menu} testID={notesnook.listitem.menu}
color={primaryColors.paragraph} color={colors.secondary.icon}
name="dots-horizontal" name="dots-horizontal"
size={SIZE.xl} size={SIZE.lg}
onPress={() => !noOpen && Properties.present(item)} onPress={() => !noOpen && Properties.present(item)}
style={{ style={{
justifyContent: "center", justifyContent: "center",
@@ -433,6 +416,7 @@ const NoteItem = ({
alignItems: "center" alignItems: "center"
}} }}
/> />
)}
</View> </View>
</> </>
); );

View File

@@ -29,7 +29,7 @@ import {
presentSheet presentSheet
} from "../../../services/event-manager"; } from "../../../services/event-manager";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events"; import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs"; import { fluidTabsRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common"; import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview"; import NotePreview from "../../note-history/preview";
@@ -66,7 +66,7 @@ export const openNote = async (
item: note item: note
}); });
if (!DDS.isTab) { if (!DDS.isTab) {
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} }
} }
}; };
@@ -101,6 +101,7 @@ export const NoteWrapper = React.memo<
onPress={() => openNote(item as Note, isTrash, isRenderedInActionSheet)} onPress={() => openNote(item as Note, isTrash, isRenderedInActionSheet)}
isSheet={isRenderedInActionSheet} isSheet={isRenderedInActionSheet}
item={item} item={item}
index={index}
color={restProps.color?.colorCode} color={restProps.color?.colorCode}
> >
<NoteItem {...restProps} item={item} index={index} isTrash={isTrash} /> <NoteItem {...restProps} item={item} index={index} isTrash={isTrash} />

View File

@@ -39,16 +39,15 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
style={{ style={{
position: "absolute", position: "absolute",
width: "110%", width: "110%",
height: "150%", height: "100%",
backgroundColor: colors.selected.background, backgroundColor: colors.selected.accent
borderLeftWidth: 5, // borderLeftWidth: 5,
borderLeftColor: isEditingNote // borderLeftColor: isEditingNote
? color // ? color
? color // ? color
: colors.selected.accent // : colors.selected.accent
: "transparent" // : "transparent"
}} }}
collapsable={false}
/> />
) : null; ) : null;
}; };

View File

@@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Item, TrashItem } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { PropsWithChildren, useRef } from "react"; import React, { PropsWithChildren, useRef } from "react";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled"; import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { useSelectionStore } from "../../../stores/use-selection-store"; import { useSelectionStore } from "../../../stores/use-selection-store";
import { DefaultAppStyles } from "../../../utils/styles";
import { Pressable } from "../../ui/pressable"; import { Pressable } from "../../ui/pressable";
import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { Item, TrashItem } from "@notesnook/core";
export function selectItem(item: Item) { export function selectItem(item: Item) {
if (useSelectionStore.getState().selectionMode === item.type) { if (useSelectionStore.getState().selectionMode === item.type) {
@@ -49,6 +49,7 @@ type SelectionWrapperProps = PropsWithChildren<{
testID?: string; testID?: string;
isSheet?: boolean; isSheet?: boolean;
color?: string; color?: string;
index?: number;
}>; }>;
const SelectionWrapper = ({ const SelectionWrapper = ({
@@ -57,10 +58,16 @@ const SelectionWrapper = ({
testID, testID,
isSheet, isSheet,
children, children,
color color,
index = 0
}: SelectionWrapperProps) => { }: SelectionWrapperProps) => {
const itemId = useRef(item.id); const itemId = useRef(item.id);
const { colors, isDark } = useThemeColors(); const { colors, isDark } = useThemeColors();
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
);
const compactMode = useIsCompactModeEnabled( const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type (item as TrashItem).itemType || item.type
); );
@@ -79,7 +86,13 @@ const SelectionWrapper = ({
return ( return (
<Pressable <Pressable
customColor={isSheet ? colors.secondary.background : "transparent"} customColor={
isEditingNote
? colors.selected.background
: isSheet
? colors.primary.hover
: "transparent"
}
testID={testID} testID={testID}
onLongPress={onLongPress} onLongPress={onLongPress}
onPress={onPress} onPress={onPress}
@@ -91,15 +104,14 @@ const SelectionWrapper = ({
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
width: "100%", width: "100%",
alignSelf: "center",
overflow: "hidden", overflow: "hidden",
paddingHorizontal: 12, paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: compactMode ? 4 : 12, paddingVertical: compactMode ? 4 : DefaultAppStyles.GAP_VERTICAL,
borderRadius: isSheet ? 10 : 0, borderRadius: isSheet ? 10 : 0,
marginBottom: isSheet ? 12 : undefined marginBottom: isSheet ? 12 : undefined
}} }}
> >
{item.type === "note" ? <Filler item={item} color={color} /> : null}
<SelectionIcon item={item} />
{children} {children}
</Pressable> </Pressable>
); );

View File

@@ -17,13 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { Dimensions, View } from "react-native"; import { Dimensions, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useMessageStore } from "../../stores/use-message-store"; import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
import { getContainerBorder, hexToRGBA } from "../../utils/colors";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Pressable } from "../ui/pressable"; import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
@@ -38,19 +38,24 @@ export const Card = ({ color }: { color?: string }) => {
(announcements && announcements.length) ? null : ( (announcements && announcements.length) ? null : (
<View <View
style={{ style={{
width: "95%" width: "100%",
paddingHorizontal: DefaultAppStyles.GAP,
marginTop: DefaultAppStyles.GAP
}} }}
> >
<Pressable <Pressable
onPress={messageBoardState.onPress} onPress={messageBoardState.onPress}
type="plain" type="plain"
style={{ style={{
paddingVertical: 12, paddingVertical: DefaultAppStyles.GAP_VERTICAL,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
paddingHorizontal: 0, paddingHorizontal: DefaultAppStyles.GAP_SMALL,
width: "100%" width: "100%",
backgroundColor: colors.secondary.background,
borderWidth: 1,
borderColor: colors.primary.border
}} }}
> >
<View <View
@@ -63,24 +68,14 @@ export const Card = ({ color }: { color?: string }) => {
<View <View
style={{ style={{
width: 40 * fontScale, width: 40 * fontScale,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.error.accent, 0.15)
: hexToRGBA(color, 0.15),
height: 40 * fontScale, height: 40 * fontScale,
borderRadius: 100, borderRadius: 100,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center"
...getContainerBorder(
messageBoardState.type === "error"
? colors.error.accent
: color || colors.primary.accent,
0.4
)
}} }}
> >
<Icon <Icon
size={SIZE.lg} size={SIZE.xxxl}
color={ color={
messageBoardState.type === "error" ? colors.error.icon : color messageBoardState.type === "error" ? colors.error.icon : color
} }
@@ -95,9 +90,6 @@ export const Card = ({ color }: { color?: string }) => {
marginRight: 10 marginRight: 10
}} }}
> >
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{messageBoardState.message}
</Paragraph>
<Paragraph <Paragraph
style={{ style={{
flexWrap: "nowrap", flexWrap: "nowrap",
@@ -107,6 +99,9 @@ export const Card = ({ color }: { color?: string }) => {
> >
{messageBoardState.actionText} {messageBoardState.actionText}
</Paragraph> </Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxs}>
{messageBoardState.message}
</Paragraph>
</View> </View>
</View> </View>

View File

@@ -34,8 +34,7 @@ import Sync from "../../services/sync";
import { RouteName } from "../../stores/use-navigation-store"; import { RouteName } from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { eScrollEvent } from "../../utils/events"; import { eScrollEvent } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { Footer } from "../list-items/footer";
import { Header } from "../list-items/headers/header"; import { Header } from "../list-items/headers/header";
import { Empty, PlaceholderData } from "./empty"; import { Empty, PlaceholderData } from "./empty";
import { ListItemWrapper } from "./list-item.wrapper"; import { ListItemWrapper } from "./list-item.wrapper";
@@ -43,6 +42,7 @@ import { ListItemWrapper } from "./list-item.wrapper";
type ListProps = { type ListProps = {
data: VirtualizedGrouping<Item> | undefined; data: VirtualizedGrouping<Item> | undefined;
dataType: Item["type"]; dataType: Item["type"];
mode?: "drawer" | "sheet";
onRefresh?: () => void; onRefresh?: () => void;
loading?: boolean; loading?: boolean;
headerTitle?: string; headerTitle?: string;
@@ -160,7 +160,7 @@ export default function List(props: ListProps) {
onScroll={onListScroll} onScroll={onListScroll}
nestedScrollEnabled={true} nestedScrollEnabled={true}
onMomentumScrollEnd={() => { onMomentumScrollEnd={() => {
tabBarRef.current?.unlock(); fluidTabsRef.current?.unlock();
}} }}
getItemType={(item: number, index: number) => { getItemType={(item: number, index: number) => {
return props.data?.type(index); return props.data?.type(index);
@@ -192,9 +192,6 @@ export default function List(props: ListProps) {
/> />
) : null ) : null
} }
ListFooterComponent={
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
}
ListHeaderComponent={ ListHeaderComponent={
<> <>
{props.CustomLisHeader ? ( {props.CustomLisHeader ? (

View File

@@ -26,7 +26,7 @@ import {
DraxListRenderItemContent DraxListRenderItemContent
} from "react-native-drax"; } from "react-native-drax";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { useSideBarDraggingStore } from "../side-menu/dragging-store"; import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
@@ -61,9 +61,9 @@ function ReorderableList<T extends { id: string }>({
const listRef = useRef<FlatList | null>(null); const listRef = useRef<FlatList | null>(null);
if (dragging) { if (dragging) {
tabBarRef.current?.lock(); fluidTabsRef.current?.lock();
} else { } else {
tabBarRef.current?.unlock(); fluidTabsRef.current?.unlock();
} }
useEffect(() => { useEffect(() => {
@@ -78,8 +78,7 @@ function ReorderableList<T extends { id: string }>({
return isHidden && !dragging ? null : ( return isHidden && !dragging ? null : (
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "row"
paddingHorizontal: 12
}} }}
> >
<View <View

View File

@@ -16,18 +16,19 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { Platform, View } from "react-native"; import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet"; import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { DDS } from "../../services/device-detection"; import { DDS } from "../../services/device-detection";
import { eSendEvent, presentSheet } from "../../services/event-manager"; import { eSendEvent, presentSheet } from "../../services/event-manager";
import { ColorValues } from "../../utils/colors"; import { ColorValues } from "../../utils/colors";
import { eOnLoadNote } from "../../utils/events"; import { eOnLoadNote } from "../../utils/events";
import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider"; import SheetProvider from "../sheet-provider";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable"; import { Pressable } from "../ui/pressable";
import { ReminderTime } from "../ui/reminder-time"; import { ReminderTime } from "../ui/reminder-time";
@@ -36,10 +37,8 @@ import Paragraph from "../ui/typography/paragraph";
import { DateMeta } from "./date-meta"; import { DateMeta } from "./date-meta";
import { Items } from "./items"; import { Items } from "./items";
import Notebooks from "./notebooks"; import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { TagStrip, Tags } from "./tags"; import { TagStrip, Tags } from "./tags";
import { tabBarRef } from "../../utils/global-refs"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { strings } from "@notesnook/intl";
const Line = ({ top = 6, bottom = 6 }) => { const Line = ({ top = 6, bottom = 6 }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
@@ -101,7 +100,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
style={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
flexShrink: 1 flexShrink: 1,
gap: 5
}} }}
> >
{item.type === "color" ? ( {item.type === "color" ? (
@@ -116,16 +116,15 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
marginRight: 10 marginRight: 10
}} }}
/> />
) : item.type === "tag" ? (
<Icon
name="pound"
size={SIZE.lg}
color={colors.primary.icon}
/>
) : null} ) : null}
<Heading size={SIZE.lg}> <Heading size={SIZE.lg}>{item.title}</Heading>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.primary.accent}>
#
</Heading>
) : null}
{item.title}
</Heading>
</View> </View>
{item.type === "note" ? ( {item.type === "note" ? (
@@ -144,7 +143,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
newTab: true newTab: true
}); });
if (!DDS.isTab) { if (!DDS.isTab) {
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} }
}} }}
/> />
@@ -202,8 +201,6 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
}} }}
/> />
<Synced item={item} close={close} />
{DDS.isTab ? ( {DDS.isTab ? (
<View <View
style={{ style={{
@@ -218,87 +215,36 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
); );
}; };
Properties.present = async (item, buttons = [], isSheet) => { Properties.present = async (item, isSheet) => {
if (!item) return; if (!item) return;
let type = item?.type; let type = item?.type;
let props = []; let dbItem;
let android = [];
switch (type) { switch (type) {
case "trash": case "trash":
props[0] = item; dbItem = item;
props.push(["delete", "restore"]);
break; break;
case "note": case "note":
android = Platform.OS === "android" ? ["pin-to-notifications"] : []; dbItem = await db.notes.note(item.id);
props[0] = await db.notes.note(item.id);
props.push([
"notebooks",
"add-reminder",
"share",
"export",
"copy",
"publish",
"pin",
"favorite",
"attachments",
"lock-unlock",
"trash",
"remove-from-notebook",
"history",
"read-only",
"reminders",
"local-only",
"duplicate",
"copy-link",
"references",
...android,
...buttons
]);
break; break;
case "notebook": case "notebook":
props[0] = await db.notebooks.notebook(item.id); dbItem = await db.notebooks.notebook(item.id);
props.push([
"edit-notebook",
"pin",
"add-shortcut",
"trash",
"default-notebook",
"add-notebook",
"move-notes",
"move-notebook"
]);
break; break;
case "tag": case "tag":
props[0] = await db.tags.tag(item.id); dbItem = await db.tags.tag(item.id);
props.push(["add-shortcut", "trash", "rename-tag"]);
break; break;
case "color": case "color":
props[0] = await db.colors.color(item.id); dbItem = await db.colors.color(item.id);
props.push([
"trash",
"rename-color",
...(useSideBarDraggingStore.getState().dragging ? [] : ["reorder"])
]);
break; break;
case "reminder": { case "reminder": {
props[0] = await db.reminders.reminder(item.id); dbItem = await db.reminders.reminder(item.id);
props.push(["edit-reminder", "trash", "disable-reminder"]);
break; break;
} }
} }
if (!props[0]) return;
presentSheet({ presentSheet({
context: isSheet ? "local" : undefined, context: isSheet ? "local" : undefined,
component: (ref, close) => ( component: (ref, close) => (
<Properties <Properties close={close} actionSheetRef={ref} item={dbItem} />
close={() => {
close();
}}
actionSheetRef={ref}
item={props[0]}
buttons={props[1]}
/>
) )
}); });
}; };

View File

@@ -1,266 +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 { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Dimensions, ScrollView, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useActions } from "../../hooks/use-actions";
import { useStoredRef } from "../../hooks/use-stored-ref";
import { DDS } from "../../services/device-detection";
import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
export const Items = ({ item, buttons, close }) => {
const { colors } = useThemeColors();
const topBarSorting = useStoredRef("topbar-sorting-ref", {});
const dimensions = useSettingStore((state) => state.dimensions);
const actions = useActions({ item, close });
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
let width = dimensions.width > 600 ? 600 : dimensions.width;
const shouldShrink =
Dimensions.get("window").fontScale > 1 &&
Dimensions.get("window").width < 450;
let columnItemsCount = DDS.isLargeTablet() ? 7 : shouldShrink ? 4 : 5;
let columnItemWidth = DDS.isTab
? (width - 12) / columnItemsCount
: (width - 12) / columnItemsCount;
const _renderRowItem = ({ item }) => (
<View
key={item.id}
testID={"icon-" + item.id}
style={{
alignItems: "center",
width: columnItemWidth,
marginBottom: 10
}}
>
<Pressable
onPress={item.func}
type={item.on ? "shade" : "secondary"}
style={{
height: columnItemWidth - 12,
width: columnItemWidth - 12,
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
textAlign: "center",
textAlignVertical: "center",
marginBottom: DDS.isTab ? 7 : 3.5
}}
>
<Icon
allowFontScaling
name={item.icon}
size={DDS.isTab ? SIZE.xxl : shouldShrink ? SIZE.xxl : SIZE.lg}
color={
item.on
? colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
<Paragraph
size={SIZE.xs}
textBreakStrategy="simple"
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</View>
);
const renderColumnItem = (item) => (
<Button
key={item.name + item.title}
buttonType={{
text: item.on
? colors.primary.accent
: item.name === "Delete" || item.name === "PermDelete"
? colors.error.paragraph
: colors.primary.paragraph
}}
onPress={item.func}
title={item.title}
icon={item.icon}
type={item.on ? "shade" : "plain"}
fontSize={SIZE.sm}
style={{
borderRadius: 0,
justifyContent: "flex-start",
alignSelf: "flex-start",
width: "100%"
}}
/>
);
const renderTopBarItem = (item, index) => {
return (
<Pressable
onPress={() => {
item.func();
setImmediate(() => {
const currentValue = topBarSorting.current[item.id] || 0;
topBarSorting.current = {
...topBarSorting.current,
[item.id]: currentValue + 1
};
});
}}
key={item.id}
testID={"icon-" + item.id}
activeOpacity={1}
style={{
alignSelf: "flex-start",
paddingHorizontal: 0,
flex: 1
}}
>
<View
onPress={item.func}
style={{
height: topBarItemWidth,
justifyContent: "center",
alignItems: "center",
textAlign: "center",
textAlignVertical: "center",
marginBottom: DDS.isTab ? 7 : 3.5,
borderRadius: 100
}}
>
<Icon
name={item.icon}
allowFontScaling
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
color={
item.on
? colors.primary.accent
: item.name === "Delete" || item.name === "PermDelete"
? colors.error.icon
: colors.secondary.icon
}
/>
</View>
<Paragraph
textBreakStrategy="simple"
size={SIZE.xxs}
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</Pressable>
);
};
const topBarItemsList = [
"pin",
"favorite",
"copy",
"share",
"lock-unlock",
"publish",
"export",
"copy-link",
"duplicate",
"local-only",
"read-only"
];
const bottomBarItemsList = [
"notebooks",
"add-reminder",
"pin-to-notifications",
"history",
"reminders",
"attachments",
"references",
"trash"
];
const topBarItems = data
.filter((item) => topBarItemsList.indexOf(item.id) > -1)
.sort((a, b) =>
topBarItemsList.indexOf(a.id) > topBarItemsList.indexOf(b.id) ? 1 : -1
)
.sort((a, b) => {
return (
(topBarSorting.current[b.id] || 0) - (topBarSorting.current[a.id] || 0)
);
});
const bottomGridItems = data
.filter((item) => bottomBarItemsList.indexOf(item.id) > -1)
.sort((a, b) =>
bottomBarItemsList.indexOf(a.id) > bottomBarItemsList.indexOf(b.id)
? 1
: -1
);
let topBarItemWidth =
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
topBarItemWidth;
if (topBarItemWidth < 60) {
topBarItemWidth = 60;
}
return item.type === "note" ? (
<>
<ScrollView
horizontal
style={{
paddingHorizontal: 12,
marginTop: 6,
marginBottom: 6
}}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingRight: 25,
gap: 15
}}
>
{topBarItems.map(renderTopBarItem)}
</ScrollView>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
alignContent: "flex-start",
marginTop: item.type !== "note" ? 10 : 0,
paddingTop: 10,
marginLeft: 6
}}
>
{bottomGridItems.map((item) => _renderRowItem({ item }))}
</View>
</>
) : (
<View data={data}>{data.map(renderColumnItem)}</View>
);
};

View File

@@ -0,0 +1,301 @@
/*
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 { Item } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Dimensions, ScrollView, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { Action, ActionId, useActions } from "../../hooks/use-actions";
import { useStoredRef } from "../../hooks/use-stored-ref";
import { DDS } from "../../services/device-detection";
import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
const TOP_BAR_ITEMS: ActionId[] = [
"pin",
"favorite",
"copy",
"share",
"lock-unlock",
"publish",
"export",
"copy-link",
"duplicate",
"local-only",
"read-only"
];
const BOTTOM_BAR_ITEMS: ActionId[] = [
"notebooks",
"add-reminder",
"pin-to-notifications",
"history",
"reminders",
"attachments",
"references",
"trash"
];
const COLUMN_BAR_ITEMS: ActionId[] = [
"select",
"add-notebook",
"edit-notebook",
"move-notes",
"move-notebook",
"pin",
"default-notebook",
"add-shortcut",
"trash"
];
export const Items = ({ item, close }: { item: Item; close: () => void }) => {
const { colors } = useThemeColors();
const topBarSorting = useStoredRef<{ [name: string]: number }>(
"topbar-sorting-ref",
{}
);
const dimensions = useSettingStore((state) => state.dimensions);
const actions = useActions({ item, close });
const selectedActions = actions.filter((i) => !i.hidden);
const deviceMode = useSettingStore((state) => state.deviceMode);
const width = Math.min(dimensions.width, 600);
const shouldShrink =
Dimensions.get("window").fontScale > 1 &&
Dimensions.get("window").width < 450;
const columnItemsCount = deviceMode === "tablet" ? 7 : shouldShrink ? 4 : 5;
const columnItemWidth =
deviceMode !== "mobile"
? (width - 16) / columnItemsCount
: (width - 16) / columnItemsCount;
const topBarItems = selectedActions
.filter((item) => TOP_BAR_ITEMS.indexOf(item.id) > -1)
.sort((a, b) =>
TOP_BAR_ITEMS.indexOf(a.id) > TOP_BAR_ITEMS.indexOf(b.id) ? 1 : -1
)
.sort((a, b) => {
return (
(topBarSorting.current[b.id] || 0) - (topBarSorting.current[a.id] || 0)
);
});
const bottomGridItems = selectedActions
.filter((item) => BOTTOM_BAR_ITEMS.indexOf(item.id) > -1)
.sort((a, b) =>
BOTTOM_BAR_ITEMS.indexOf(a.id) > BOTTOM_BAR_ITEMS.indexOf(b.id) ? 1 : -1
);
const columnItems = selectedActions
.filter((item) => COLUMN_BAR_ITEMS.indexOf(item.id) > -1)
.sort((a, b) =>
COLUMN_BAR_ITEMS.indexOf(a.id) > COLUMN_BAR_ITEMS.indexOf(b.id) ? 1 : -1
);
const topBarItemHeight = Math.min(
(width - (topBarItems.length * 10 + 14)) / topBarItems.length,
60
);
const renderRowItem = React.useCallback(
({ item }: { item: Action }) => (
<View
key={item.id}
testID={"icon-" + item.id}
style={{
alignItems: "center",
width: columnItemWidth - 8
}}
>
<Pressable
onPress={item.onPress}
type={item.checked ? "shade" : "secondary"}
style={{
height: columnItemWidth - 8,
width: columnItemWidth - 8,
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
marginBottom: DDS.isTab ? 7 : 3.5
}}
>
<Icon
allowFontScaling
name={item.icon}
size={DDS.isTab ? SIZE.xxl : shouldShrink ? SIZE.xxl : SIZE.lg}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id.match(/(delete|trash)/g)
? colors.error.icon
: colors.secondary.icon
}
/>
</Pressable>
<Paragraph
size={SIZE.xxs}
textBreakStrategy="simple"
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</View>
),
[
colors.error.icon,
colors.primary.accent,
colors.secondary.icon,
columnItemWidth,
shouldShrink
]
);
const renderColumnItem = React.useCallback(
(item: Action) => (
<Button
key={item.id}
buttonType={{
text: item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.paragraph
: colors.primary.paragraph
}}
onPress={item.onPress}
title={item.title}
icon={item.icon}
type={item.checked ? "inverted" : "plain"}
fontSize={SIZE.sm}
style={{
borderRadius: 0,
justifyContent: "flex-start",
alignSelf: "flex-start",
width: "100%"
}}
/>
),
[colors.error.paragraph, colors.primary.accent, colors.primary.paragraph]
);
const renderTopBarItem = React.useCallback(
(item: Action) => {
return (
<Pressable
onPress={() => {
item.onPress();
setImmediate(() => {
const currentValue = topBarSorting.current[item.id] || 0;
topBarSorting.current = {
...topBarSorting.current,
[item.id]: currentValue + 1
};
});
}}
key={item.id}
testID={"icon-" + item.id}
style={{
alignSelf: "flex-start",
paddingHorizontal: 0,
flex: 1
}}
>
<View
style={{
height: topBarItemHeight,
justifyContent: "center",
alignItems: "center",
marginBottom: DDS.isTab ? 7 : 3.5,
borderRadius: 100
}}
>
<Icon
name={item.icon}
allowFontScaling
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
color={
item.checked
? item.activeColor || colors.primary.accent
: item.id === "delete" || item.id === "trash"
? colors.error.icon
: colors.secondary.icon
}
/>
</View>
<Paragraph
textBreakStrategy="simple"
size={SIZE.xxs}
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</Pressable>
);
},
[
colors.error.icon,
colors.primary.accent,
colors.secondary.icon,
topBarItemHeight,
topBarSorting
]
);
return item.type === "note" ? (
<>
<ScrollView
horizontal
style={{
paddingHorizontal: 16,
marginTop: 6,
marginBottom: 6
}}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingRight: 25,
gap: 15
}}
>
{topBarItems.map(renderTopBarItem)}
</ScrollView>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
marginTop: 10,
gap: 5,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
{bottomGridItems.map((item) => renderRowItem({ item }))}
</View>
</>
) : (
<View>{columnItems.map(renderColumnItem)}</View>
);
};

View File

@@ -19,75 +19,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { View, useWindowDimensions } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useUserStore } from "../../stores/use-user-store"; import { useUserStore } from "../../stores/use-user-store";
import { openLinkInBrowser } from "../../utils/functions"; import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time"; import { sleep } from "../../utils/time";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const Synced = ({ item, close }) => { export const Synced = ({ item, close }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const user = useUserStore((state) => state.user); const user = useUserStore((state) => state.user);
const lastSynced = useUserStore((state) => state.lastSynced); const lastSynced = useUserStore((state) => state.lastSynced);
const dimensions = useWindowDimensions();
const shouldShrink = dimensions.fontScale > 1 && dimensions.width < 450;
return user && lastSynced >= item.dateModified ? ( return user && lastSynced >= item.dateModified ? (
<View <Button
style={{ style={{
paddingVertical: 0,
width: "100%",
paddingHorizontal: 12,
alignItems: "center", alignItems: "center",
flexDirection: "row", flexDirection: "row",
justifyContent: "flex-start", justifyContent: "flex-start",
alignSelf: "center", alignSelf: "flex-start",
paddingTop: 10, height: "auto",
marginTop: 10, padding: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderTopWidth: 1, paddingHorizontal: DefaultAppStyles.GAP_SMALL
borderTopColor: colors.primary.border
}} }}
> fontSize={SIZE.xxxs}
<Icon iconSize={SIZE.xs}
name="shield-key-outline" icon="shield-key-outline"
color={colors.primary.accent} type="shade"
size={shouldShrink ? SIZE.xxl : SIZE.xxxl} title="Encrypted and synced"
/>
<View
style={{
flex: 1,
marginLeft: 5,
flexShrink: 1
}}
>
<Heading
color={colors.primary.heading}
size={SIZE.xs}
style={{
flexWrap: "wrap"
}}
>
{strings.noteSyncedNoticeHeading()}
</Heading>
{shouldShrink ? null : (
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xs}
color={colors.primary.paragraph}
>
{strings.noteSyncedNoticeDesc(item.itemType || item.type)}
</Paragraph>
)}
</View>
<Button
onPress={async () => { onPress={async () => {
try { try {
close(); close();
@@ -100,11 +57,6 @@ export const Synced = ({ item, close }) => {
console.error(e); console.error(e);
} }
}} }}
title={strings.learnMore()}
fontSize={SIZE.xs}
height={30}
type="secondaryAccented"
/> />
</View>
) : null; ) : null;
}; };

View File

@@ -35,18 +35,17 @@ import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store"; import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store"; import { useSelectionStore } from "../../stores/use-selection-store";
import { deleteItems } from "../../utils/functions"; import { deleteItems } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { updateNotebook } from "../../utils/notebooks"; import { updateNotebook } from "../../utils/notebooks";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time"; import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions"; import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to"; import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes"; import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags"; import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebookSheet } from "../sheets/move-notebook"; import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
export const SelectionHeader = React.memo( export const SelectionHeader = React.memo(
({ ({
@@ -75,9 +74,9 @@ export const SelectionHeader = React.memo(
useEffect(() => { useEffect(() => {
if (selectionMode) { if (selectionMode) {
tabBarRef.current?.lock(); fluidTabsRef.current?.lock();
} else { } else {
tabBarRef.current?.unlock(); fluidTabsRef.current?.unlock();
} }
}, [selectionMode]); }, [selectionMode]);
@@ -139,61 +138,24 @@ export const SelectionHeader = React.memo(
<View <View
style={{ style={{
width: "100%", width: "100%",
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "android" ? insets.top : null,
backgroundColor: colors.primary.background, backgroundColor: colors.primary.background,
justifyContent: "space-between", paddingVertical: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center", alignItems: "center",
flexDirection: "row", flexDirection: "row",
zIndex: 999, zIndex: 999,
paddingHorizontal: 12 paddingHorizontal: DefaultAppStyles.GAP,
position: "absolute",
bottom: 0,
borderTopWidth: 1,
borderColor: colors.primary.border,
justifyContent: "space-between"
}} }}
> >
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center", alignItems: "center",
borderRadius: 100 gap: DefaultAppStyles.GAP_SMALL
}}
>
<IconButton
style={{
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
clearSelection();
}}
color={colors.primary.icon}
name="close"
/>
<View
style={{
height: 40,
borderRadius: 100,
paddingHorizontal: 16,
justifyContent: "center",
flexDirection: "row",
alignItems: "center"
}}
>
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
{selectedItemsList.length}
</Heading>
</View>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center"
}} }}
> >
<IconButton <IconButton
@@ -202,42 +164,14 @@ export const SelectionHeader = React.memo(
.getState() .getState()
.setAll(allSelected ? [] : [...((await items?.ids()) || [])]); .setAll(allSelected ? [] : [...((await items?.ids()) || [])]);
}} }}
tooltipText="Select all" size={SIZE.lg}
tooltipPosition={4} color={allSelected ? colors.primary.accent : colors.primary.icon}
style={{
marginLeft: 10
}}
color={
allSelected ? colors.primary.accent : colors.primary.paragraph
}
name="select-all" name="select-all"
/> />
{selectedItemsList.length ? ( {!selectedItemsList.length
<Menu ? null
ref={menuRef} : [
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
color={colors.primary.paragraph}
/>
}
>
{[
{ {
title: strings.move(), title: strings.move(),
onPress: async () => { onPress: async () => {
@@ -341,20 +275,12 @@ export const SelectionHeader = React.memo(
} }
].map((item) => ].map((item) =>
!item.visible ? null : ( !item.visible ? null : (
<Button <IconButton
style={{ size={SIZE.lg}
justifyContent: "flex-start",
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain" type="plain"
buttonType={{ name={item.icon}
text: contextMenuColors.primary.paragraph
}}
icon={item.icon}
key={item.title} key={item.title}
title={item.title} color={colors.primary.icon}
onPress={async () => { onPress={async () => {
//@ts-ignore //@ts-ignore
menuRef.current?.hide(); menuRef.current?.hide();
@@ -364,9 +290,16 @@ export const SelectionHeader = React.memo(
/> />
) )
)} )}
</Menu>
) : null}
</View> </View>
<IconButton
size={SIZE.lg}
onPress={() => {
clearSelection();
}}
color={colors.primary.icon}
name="close"
/>
</View> </View>
); );
} }

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Notebook } from "@notesnook/core"; import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native"; import { TextInput, View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids"; import { notesnook } from "../../../../e2e/test.ids";
@@ -30,17 +31,16 @@ import {
} from "../../../services/event-manager"; } from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store"; import { useMenuStore } from "../../../stores/use-menu-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnNotebookUpdated } from "../../../utils/events";
import { getParentNotebookId } from "../../../utils/notebooks";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import Input from "../../ui/input"; import Input from "../../ui/input";
import Seperator from "../../ui/seperator"; import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import { MoveNotes } from "../move-notes/movenote"; import { MoveNotes } from "../move-notes/movenote";
import { eOnNotebookUpdated } from "../../../utils/events";
import { getParentNotebookId } from "../../../utils/notebooks";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { strings } from "@notesnook/intl";
export const AddNotebookSheet = ({ export const AddNotebookSheet = ({
notebook, notebook,
@@ -97,11 +97,10 @@ export const AddNotebookSheet = ({
parentNotebook?.id || parentNotebook?.id ||
(await getParentNotebookId(notebook?.id || (id as string))); (await getParentNotebookId(notebook?.id || (id as string)));
eSendEvent(eOnNotebookUpdated, parent); eSendEvent(eOnNotebookUpdated, parent || notebook?.id);
if (notebook) {
setImmediate(() => { if (!parent) {
eSendEvent(eOnNotebookUpdated, notebook.id); useNotebookStore.getState().refresh();
});
} }
if (!notebook && showMoveNotesOnComplete && id) { if (!notebook && showMoveNotesOnComplete && id) {

View File

@@ -17,6 +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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Notebook, VirtualizedGrouping } from "@notesnook/core"; import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
@@ -45,7 +46,6 @@ import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator"; import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook"; import { AddNotebookSheet } from "../add-notebook";
import { strings } from "@notesnook/intl";
const useNotebookExpandedStore = create<{ const useNotebookExpandedStore = create<{
expanded: { expanded: {
@@ -119,14 +119,19 @@ export const MoveNotebookSheet = ({
}, },
notebook notebook
); );
}
await db.relations.add(selectedNotebook, notebook);
if (parent) {
eSendEvent(eOnNotebookUpdated, parent); eSendEvent(eOnNotebookUpdated, parent);
} }
await db.relations.add(selectedNotebook, notebook);
eSendEvent(eOnNotebookUpdated, selectedNotebook.id);
eSendEvent(eOnNotebookUpdated, notebook.id);
} }
if (!parent) {
useNotebookStore.getState().refresh(); useNotebookStore.getState().refresh();
} else {
eSendEvent(eOnNotebookUpdated, selectedNotebook.id);
}
close?.(); close?.();
} }
}); });

View File

@@ -34,7 +34,7 @@ import { useDBItem, useNoteLocked } from "../../../hooks/use-db-item";
import { eSendEvent, presentSheet } from "../../../services/event-manager"; import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnLoadNote } from "../../../utils/events"; import { eOnLoadNote } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs"; import { fluidTabsRef } from "../../../utils/global-refs";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import SheetProvider from "../../sheet-provider"; import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
@@ -423,7 +423,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
item: note, item: note,
blockId: blockId blockId: blockId
}); });
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
close?.(); close?.();
}} }}
reference={item as Note} reference={item as Note}

View File

@@ -17,45 +17,55 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {
GroupingKey,
GroupOptions,
ItemType,
SortOptions
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react"; import React, { useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { eSendEvent } from "../../../services/event-manager"; import { eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import { useThemeColors } from "@notesnook/theme"; import { RouteName } from "../../../stores/use-navigation-store";
import { GROUP, SORT } from "../../../utils/constants"; import { GROUP, SORT } from "../../../utils/constants";
import { eGroupOptionsUpdated, refreshNotesPage } from "../../../utils/events"; import { eGroupOptionsUpdated, refreshNotesPage } from "../../../utils/events";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import AppIcon from "../../ui/AppIcon";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator"; import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import { strings } from "@notesnook/intl"; import Paragraph from "../../ui/typography/paragraph";
const Sort = ({ type, screen }) => { const Sort = ({ type, screen }: { type: ItemType; screen: RouteName }) => {
const isTopicSheet = screen === "TopicSheet";
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const [groupOptions, setGroupOptions] = useState( const [groupOptions, setGroupOptions] = useState(
db.settings.getGroupOptions(screen === "Notes" ? "home" : type + "s") db.settings.getGroupOptions(
screen === "Notes" ? "home" : ((type + "s") as GroupingKey)
)
); );
const updateGroupOptions = async (_groupOptions) => { const updateGroupOptions = async (_groupOptions: GroupOptions) => {
const groupType = screen === "Notes" ? "home" : type + "s"; const groupType =
screen === "Notes" ? "home" : ((type + "s") as GroupingKey);
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions); await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions); setGroupOptions(_groupOptions);
setTimeout(() => { setTimeout(() => {
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen); Navigation.queueRoutesForUpdate(screen);
eSendEvent(eGroupOptionsUpdated, groupType); eSendEvent(eGroupOptionsUpdated, groupType);
eSendEvent(refreshNotesPage); eSendEvent(refreshNotesPage);
}, 1); }, 1);
}; };
const setOrderBy = async () => { const setOrderBy = async () => {
let _groupOptions = { const _groupOptions: GroupOptions = {
...groupOptions, ...groupOptions,
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc" sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
}; };
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions); await updateGroupOptions(_groupOptions);
}; };
@@ -64,7 +74,8 @@ const Sort = ({ type, screen }) => {
style={{ style={{
width: "100%", width: "100%",
backgroundColor: colors.primary.background, backgroundColor: colors.primary.background,
justifyContent: "space-between" justifyContent: "space-between",
gap: DefaultAppStyles.GAP_SMALL
}} }}
> >
<View <View
@@ -72,11 +83,12 @@ const Sort = ({ type, screen }) => {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
paddingHorizontal: 12 paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}} }}
> >
<Heading <Heading
size={SIZE.xl} size={SIZE.md}
style={{ style={{
alignSelf: "center" alignSelf: "center"
}} }}
@@ -105,34 +117,22 @@ const Sort = ({ type, screen }) => {
? "sort-ascending" ? "sort-ascending"
: "sort-descending" : "sort-descending"
} }
height={25} height={30}
iconPosition="right" iconPosition="right"
fontSize={SIZE.sm - 1} fontSize={SIZE.sm}
type="transparent" type="plain"
buttonType={{
text: colors.primary.accent
}}
style={{ style={{
borderRadius: 100, paddingHorizontal: DefaultAppStyles.GAP_SMALL
paddingHorizontal: 6
}} }}
onPress={setOrderBy} onPress={setOrderBy}
/> />
</View> </View>
<Seperator />
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "column",
justifyContent: "flex-start", justifyContent: "flex-start",
flexWrap: "wrap", borderBottomColor: colors.primary.border
borderBottomWidth: isTopicSheet ? 0 : 1,
borderBottomColor: colors.primary.border,
marginBottom: 12,
paddingHorizontal: 12,
paddingBottom: 12,
alignItems: "center"
}} }}
> >
{groupOptions?.groupBy === "abc" ? ( {groupOptions?.groupBy === "abc" ? (
@@ -149,105 +149,118 @@ const Sort = ({ type, screen }) => {
) : ( ) : (
Object.keys(SORT).map((item) => Object.keys(SORT).map((item) =>
(item === "dueDate" && screen !== "Reminders") || (item === "dueDate" && screen !== "Reminders") ||
((screen !== "Tags" || screen !== "Reminders") && (screen !== "Tags" &&
screen !== "Reminders" &&
item === "dateModified") || item === "dateModified") ||
((screen === "Tags" || screen === "Reminders") && ((screen === "Tags" || screen === "Reminders") &&
item === "dateEdited") ? null : ( item === "dateEdited") ? null : (
<Button <Pressable
key={item} key={item}
type={groupOptions?.sortBy === item ? "selected" : "plain"} type={groupOptions?.sortBy === item ? "selected" : "plain"}
title={strings.sortByStrings[item]()} noborder
height={40}
iconPosition="left"
icon={groupOptions?.sortBy === item ? "check" : null}
style={{ style={{
marginRight: 10, width: "100%",
paddingHorizontal: 8 justifyContent: "space-between",
height: 40,
flexDirection: "row",
borderRadius: 0,
paddingHorizontal: DefaultAppStyles.GAP
}} }}
buttonType={{
text:
groupOptions?.sortBy === item
? colors.primary.accent
: colors.secondary.paragraph
}}
fontSize={SIZE.sm}
onPress={async () => { onPress={async () => {
let _groupOptions = { const _groupOptions: GroupOptions = {
...groupOptions, ...groupOptions,
sortBy: type === "trash" ? "dateDeleted" : item sortBy:
type === "trash"
? "dateDeleted"
: (item as SortOptions["sortBy"])
}; };
if (screen === "TopicSheet") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions); await updateGroupOptions(_groupOptions);
}} }}
iconSize={SIZE.md} >
<Paragraph>
{strings.sortByStrings[
item as keyof typeof strings.sortByStrings
]()}
</Paragraph>
{groupOptions.sortBy === item ? (
<AppIcon
size={SIZE.lg}
name="check"
color={colors.selected.accent}
/> />
) : null}
</Pressable>
) )
) )
)} )}
</View> </View>
{isTopicSheet ? null : (
<> <>
<Heading <View
style={{ style={{
marginLeft: 12 flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}} }}
size={SIZE.lg}
> >
{strings.groupBy()} <Heading size={SIZE.md}>{strings.groupBy()}</Heading>
</Heading> </View>
<Seperator />
<View <View
style={{ style={{
borderRadius: 0, borderRadius: 0,
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap"
paddingHorizontal: 12
}} }}
> >
{Object.keys(GROUP).map((item) => ( {Object.keys(GROUP).map((item) => (
<Button <Pressable
key={item} key={item}
testID={"btn-" + item}
type={ type={
groupOptions?.groupBy === GROUP[item] ? "selected" : "plain" groupOptions?.groupBy === GROUP[item as keyof typeof GROUP]
? "selected"
: "plain"
} }
buttonType={{ noborder
text: style={{
groupOptions?.groupBy === GROUP[item] width: "100%",
? colors.primary.accent justifyContent: "space-between",
: colors.secondary.paragraph height: 40,
flexDirection: "row",
borderRadius: 0,
paddingHorizontal: DefaultAppStyles.GAP
}} }}
onPress={async () => { onPress={async () => {
let _groupOptions = { const _groupOptions: GroupOptions = {
...groupOptions, ...groupOptions,
groupBy: GROUP[item] sortBy:
type === "trash"
? "dateDeleted"
: (item as SortOptions["sortBy"])
}; };
await updateGroupOptions(_groupOptions);
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "desc";
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions?.groupBy === GROUP[item] ? "check" : null}
title={strings.groupByStrings[item]()}
style={{
paddingHorizontal: 8,
marginBottom: 10,
marginRight: 10
}} }}
>
<Paragraph>
{strings.groupByStrings[
item as keyof typeof strings.groupByStrings
]()}
</Paragraph>
{groupOptions.groupBy === item ? (
<AppIcon
size={SIZE.lg}
name="check"
color={colors.selected.accent}
/> />
) : null}
</Pressable>
))} ))}
</View> </View>
</> </>
)}
</View> </View>
); );
}; };

View File

@@ -0,0 +1,304 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import React from "react";
import { ActivityIndicator, Image, View } from "react-native";
import { useSheetRef } from "react-native-actions-sheet";
import useSyncProgress from "../../../hooks/use-sync-progress";
import { presentSheet } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { SyncStatus, useUserStore } from "../../../stores/use-user-store";
import { SIZE } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { Card } from "../../list/card";
import AppIcon from "../../ui/AppIcon";
import { Button } from "../../ui/button";
import { Pressable } from "../../ui/pressable";
import { TimeSince } from "../../ui/time-since";
import Paragraph from "../../ui/typography/paragraph";
export const UserSheet = () => {
const ref = useSheetRef();
const { colors } = useThemeColors();
const [userProfile, user, syncing, lastSyncStatus, lastSynced] = useUserStore(
(state) => [
state.profile,
state.user,
state.syncing,
state.lastSyncStatus,
state.lastSynced
]
);
const { isInternetReachable } = useNetInfo();
const isOffline = !isInternetReachable;
const { progress } = useSyncProgress();
return (
<View
style={{
width: "100%",
justifyContent: "center",
paddingHorizontal: DefaultAppStyles.GAP,
gap: DefaultAppStyles.GAP
}}
>
{user ? (
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<Image
source={{
uri: userProfile?.profilePicture
}}
style={{
width: 40,
height: 40,
borderRadius: 10
}}
/>
<View
style={{
flex: 1,
flexDirection: "row",
justifyContent: "space-between"
}}
>
<View style={{ marginLeft: 10 }}>
<Paragraph size={SIZE.xs}>{userProfile?.fullName}</Paragraph>
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xxs}
color={colors.secondary.heading}
>
<AppIcon
name="checkbox-blank-circle"
size={10}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
: isOffline
? colors.static.orange
: colors.success.icon
}
/>{" "}
{!user ? (
strings.notLoggedIn()
) : lastSynced && lastSynced !== "Never" ? (
<>
{syncing
? `${strings.syncing()} ${
progress ? `(${progress.current})` : ""
}`
: lastSyncStatus === SyncStatus.Failed
? strings.syncFailed()
: strings.synced()}{" "}
{!syncing ? (
<TimeSince
style={{
fontSize: SIZE.xxs,
color: colors.secondary.paragraph
}}
updateFrequency={30 * 1000}
time={lastSynced as number}
/>
) : null}
{isOffline ? ` (${strings.offline()})` : ""}
</>
) : (
strings.never()
)}
</Paragraph>
</View>
{syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xxl}
/>
) : null}
</View>
</View>
) : (
<View
style={{
width: "100%"
}}
>
<Card />
</View>
)}
{user ? (
<View
style={{
paddingVertical: DefaultAppStyles.GAP_SMALL,
gap: DefaultAppStyles.GAP,
borderRadius: 10,
backgroundColor: colors.primary.background
}}
>
<View
style={{
gap: DefaultAppStyles.GAP_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "space-between"
}}
>
<Paragraph size={SIZE.xxs}>{strings.storage()}</Paragraph>
<Paragraph size={SIZE.xxs}>50/100MB {strings.used()}</Paragraph>
</View>
<View
style={{
backgroundColor: colors.secondary.background,
width: "100%",
height: 5,
borderRadius: 10
}}
>
<View
style={{
backgroundColor: colors.static.black,
height: 5,
width: "50%",
borderRadius: 10
}}
/>
</View>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
padding: DefaultAppStyles.GAP_SMALL,
borderRadius: 10
}}
>
<View>
<Paragraph size={SIZE.sm}>{strings.freePlan()}</Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxxs}>
{strings.viewAllLimits()}
<AppIcon name="information" size={SIZE.xxxs} />
</Paragraph>
</View>
<Button
title={strings.upgradeNow()}
onPress={() => {}}
type="accent"
fontSize={SIZE.xs}
style={{
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
height: "auto",
paddingVertical: DefaultAppStyles.GAP_SMALL
}}
/>
</View>
</View>
) : null}
<View
style={{
borderBottomWidth: 1,
height: 1,
borderColor: colors.primary.border
}}
></View>
<View
style={{
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
{[
{
icon: "account-outline",
title: strings.editProfile(),
onPress: () => {},
hidden: !user
},
{
icon: "cog-outline",
title: strings.settings(),
onPress: () => {
ref.current?.hide();
Navigation.closeDrawer();
Navigation.navigate("Settings");
}
},
{
icon: "logout",
title: strings.logout(),
onPress: () => {
ref.current?.hide();
},
hidden: !user
}
].map((item) =>
item.hidden ? null : (
<Pressable
key={item.title}
style={{
paddingVertical: DefaultAppStyles.GAP_SMALL,
alignItems: "center",
flexDirection: "row",
justifyContent: "flex-start",
gap: DefaultAppStyles.GAP_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
}}
onPress={() => {
item.onPress();
}}
>
<AppIcon
color={colors.secondary.icon}
name={item.icon}
size={SIZE.xl}
/>
<Paragraph>{item.title}</Paragraph>
</Pressable>
)
)}
</View>
</View>
);
};
UserSheet.present = () => {
presentSheet({
component: <UserSheet />
});
};

View File

@@ -18,22 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Color } from "@notesnook/core"; import { Color } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme"; import React, { useEffect, useMemo } from "react";
import React, { useEffect } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { ColoredNotes } from "../../screens/notes/colored"; import { ColoredNotes } from "../../screens/notes/colored";
import Navigation from "../../services/navigation"; import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store"; import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE, normalize } from "../../utils/size"; import { SideMenuItem } from "../../utils/menu-items";
import ReorderableList from "../list/reorderable-list"; import ReorderableList from "../list/reorderable-list";
import { Properties } from "../properties"; import { Properties } from "../properties";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useSideBarDraggingStore } from "./dragging-store"; import { useSideBarDraggingStore } from "./dragging-store";
import { MenuItem } from "./menu-item";
export const ColorSection = React.memo( export const ColorSection = React.memo(
function ColorSection() { function ColorSection() {
@@ -54,6 +50,54 @@ export const ColorSection = React.memo(
} }
}, [loading, setColorNotes]); }, [loading, setColorNotes]);
const onPress = React.useCallback((item: SideMenuItem) => {
ColoredNotes.navigate(item.data as Color, false);
setImmediate(() => {
Navigation.closeDrawer();
});
}, []);
const onLongPress = React.useCallback((item: SideMenuItem) => {
if (useSideBarDraggingStore.getState().dragging) return;
Properties.present(item.data as Color);
}, []);
const renderIcon = React.useCallback((item: SideMenuItem, size: number) => {
return (
<View
style={{
width: size,
height: size,
justifyContent: "center",
alignItems: "center"
}}
>
<View
style={{
width: size - 5,
height: size - 5,
backgroundColor: (item.data as Color).colorCode,
borderRadius: 100
}}
/>
</View>
);
}, []);
const menuItems = useMemo(
() =>
colorNotes.map((item) => ({
id: item.id,
title: item.title,
icon: "circle",
dataType: "color",
data: item,
onPress: onPress,
onLongPress: onLongPress
})) as SideMenuItem[],
[colorNotes, onPress, onLongPress]
);
return ( return (
<ReorderableList <ReorderableList
onListOrderChanged={(data) => { onListOrderChanged={(data) => {
@@ -66,103 +110,16 @@ export const ColorSection = React.memo(
itemOrder={order} itemOrder={order}
hiddenItems={hiddensItems} hiddenItems={hiddensItems}
alwaysBounceVertical={false} alwaysBounceVertical={false}
data={colorNotes} data={menuItems}
style={{ style={{
width: "100%" width: "100%"
}} }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderDraggableItem={({ item }) => { renderDraggableItem={({ item }) => {
return <ColorItem key={item.id} item={item} />; return <MenuItem item={item} renderIcon={renderIcon} />;
}} }}
/> />
); );
}, },
() => true () => true
); );
const ColorItem = React.memo(
function ColorItem({ item }: { item: Color }) {
const { colors, isDark } = useThemeColors();
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.id
);
const onPress = (item: Color) => {
ColoredNotes.navigate(item, false);
setImmediate(() => {
Navigation.closeDrawer();
});
};
const onLongPress = () => {
if (useSideBarDraggingStore.getState().dragging) return;
Properties.present(item);
};
return (
<Pressable
customColor={isFocused ? "rgba(0,0,0,0.04)" : "transparent"}
onLongPress={onLongPress}
customSelectedColor={item.colorCode}
customAlpha={!isDark ? -0.02 : 0.02}
customOpacity={0.12}
onPress={() => onPress(item)}
style={{
width: "100%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 8,
justifyContent: "space-between",
alignItems: "center",
height: normalize(50),
marginBottom: 5
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
width: 30,
justifyContent: "center",
alignItems: "flex-start"
}}
>
<View
style={{
width: SIZE.lg - 2,
height: SIZE.lg - 2,
backgroundColor: item.colorCode,
borderRadius: 100,
justifyContent: "center",
marginRight: 10
}}
/>
</View>
{isFocused ? (
<Heading color={colors.selected.heading} size={SIZE.md}>
{item.title}
</Heading>
) : (
<Paragraph color={colors.primary.paragraph} size={SIZE.md}>
{item.title}
</Paragraph>
)}
</View>
</Pressable>
);
},
(prev, next) => {
if (!next.item) return false;
if (prev.item?.title !== next.item.title) return false;
if (prev.item?.dateModified !== next.item?.dateModified) return false;
if (prev.item?.id !== next.item?.id) return false;
return true;
}
);

View File

@@ -18,162 +18,351 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react"; import React from "react";
import { View } from "react-native"; import { Image, View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax"; import {
NavigationState,
Route,
SceneMap,
SceneRendererProps,
TabDescriptor,
TabView
} from "react-native-tab-view";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database"; import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets"; import Navigation from "../../services/navigation";
import { eSendEvent } from "../../services/event-manager";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store"; import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants"; import { deleteItems } from "../../utils/functions";
import { eOpenPremiumDialog } from "../../utils/events"; import { SIZE } from "../../utils/size";
import { MenuItemsList } from "../../utils/menu-items"; import { DefaultAppStyles } from "../../utils/styles";
import ReorderableList from "../list/reorderable-list"; import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button"; import { UserSheet } from "../sheets/user";
import { ColorSection } from "./color-section"; import { Pressable } from "../ui/pressable";
import { useSideBarDraggingStore } from "./dragging-store"; import Paragraph from "../ui/typography/paragraph";
import { MenuItem } from "./menu-item"; import { SideMenuHome } from "./side-menu-home";
import { PinnedSection } from "./pinned-section"; import { SideMenuNotebooks } from "./side-menu-notebooks";
import { UserStatus } from "./user-status"; import { SideMenuTags } from "./side-menu-tags";
import { strings } from "@notesnook/intl"; import {
useSideMenuNotebookSelectionStore,
useSideMenuTagsSelectionStore
} from "./stores";
import { SafeAreaView } from "react-native-safe-area-context";
import { rootNavigatorRef } from "../../utils/global-refs";
const renderScene = SceneMap({
home: SideMenuHome,
notebooks: SideMenuNotebooks,
tags: SideMenuTags,
settings: () => null
});
export const SideMenu = React.memo( export const SideMenu = React.memo(
function SideMenu() { function SideMenu() {
const { colors, isDark } = useThemeColors(); const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets(); const [index, setIndex] = React.useState(0);
const subscriptionType = useUserStore( const [routes] = React.useState<Route[]>([
(state) => state.user?.subscription?.type {
); key: "home",
const isAppLoading = useSettingStore((state) => state.isAppLoading); title: "Home"
const dragging = useSideBarDraggingStore((state) => state.dragging); },
const [order, hiddensItems] = useMenuStore((state) => [ {
state.order["routes"], key: "notebooks",
state.hiddenItems["routes"] title: "Notebooks"
},
{
key: "tags",
title: "Tags"
},
{
key: "settings",
title: "Settings"
}
]); ]);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const pro = {
name: "Notesnook Pro",
icon: "crown",
func: () => {
eSendEvent(eOpenPremiumDialog);
}
};
const renderItem = useCallback(
() => (
<>
<ReorderableList
onListOrderChanged={(data) => {
db.settings.setSideBarOrder("routes", data);
}}
onHiddenItemsChanged={(data) => {
db.settings.setSideBarHiddenItems("routes", data);
}}
itemOrder={order}
hiddenItems={hiddensItems}
alwaysBounceVertical={false}
data={MenuItemsList}
style={{
width: "100%"
}}
showsVerticalScrollIndicator={false}
renderDraggableItem={({ item, index }) => {
return ( return (
<MenuItem <SafeAreaView
key={item.name}
item={{
...item,
title:
strings.routes[
item.name as keyof typeof strings.routes
]?.() || item.name
}}
testID={item.name}
index={index}
/>
);
}}
/>
<ColorSection />
<PinnedSection />
</>
),
[order, hiddensItems]
);
return !isAppLoading && introCompleted ? (
<View
style={{ style={{
height: "100%", flex: 1,
width: "100%",
backgroundColor: colors.primary.background backgroundColor: colors.primary.background
}} }}
> >
<View <TabView
style={{ navigationState={{ index, routes }}
height: "100%", renderTabBar={(props) => <TabBar {...props} />}
width: "100%", tabBarPosition="bottom"
backgroundColor: colors.primary.background, renderScene={renderScene}
paddingTop: insets.top onIndexChange={setIndex}
}} swipeEnabled={false}
> animationEnabled={false}
<DraxProvider>
<DraxScrollView nestedScrollEnabled={false}>
{renderItem()}
</DraxScrollView>
</DraxProvider>
<View
style={{
paddingHorizontal: 12
}}
>
{subscriptionType === SUBSCRIPTION_STATUS.TRIAL ||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
<MenuItem testID={pro.name} key={pro.name} item={pro} index={0} />
) : null}
</View>
{dragging ? (
<View
style={{
paddingHorizontal: 12
}}
>
<Button
type="secondaryAccented"
style={{
flexDirection: "row",
borderRadius: 5,
marginBottom: 12,
justifyContent: "flex-start",
alignItems: "center",
paddingHorizontal: 12,
marginTop: 5,
paddingVertical: 6,
width: "100%"
}}
icon="close"
title={strings.stopReordering()}
onPress={() => {
useSideBarDraggingStore.setState({
dragging: false
});
}}
/> />
</View> </SafeAreaView>
) : ( );
<UserStatus />
)}
</View>
</View>
) : null;
}, },
() => true () => true
); );
const SettingsIcon = (props: {
route: Route;
isFocused: boolean;
jumpTo: (routeName: string) => void;
}) => {
const { colors } = useThemeColors();
const userProfile = useUserStore((state) => state.profile);
const user = useUserStore((state) => state.user);
return (
<Pressable
key={props.route.key}
onPress={() => {
rootNavigatorRef.navigate("Settings");
// if (!user) {
// rootNavigatorRef.navigate("Settings");
// return;
// }
// UserSheet.present();
}}
style={{
borderRadius: 10,
paddingVertical: 2,
width: "25%"
}}
type={props.isFocused ? "selected" : "plain"}
>
{userProfile?.profilePicture ? (
<Image
source={{
uri: userProfile?.profilePicture
}}
style={{
width: SIZE.lg,
height: SIZE.lg,
borderRadius: 100
}}
/>
) : (
<Icon
name="cog-outline"
color={props.isFocused ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
)}
<Paragraph
color={
props.isFocused
? colors.primary.paragraph
: colors.secondary.paragraph
}
style={{
opacity: props.isFocused ? 1 : 0.6
}}
size={SIZE.xxxs - 1}
>
{userProfile?.fullName
? userProfile.fullName.split(" ")[0]
: props.route.title}
</Paragraph>
</Pressable>
);
};
const TabBar = (
props: SceneRendererProps & {
navigationState: NavigationState<Route>;
options: Record<string, TabDescriptor<Route>> | undefined;
}
) => {
const { colors } = useThemeColors();
const notebookSelectionEnabled = useSideMenuNotebookSelectionStore(
(state) => state.enabled
);
const tagSelectionEnabled = useSideMenuTagsSelectionStore(
(state) => state.enabled
);
const isSelectionEnabled = notebookSelectionEnabled || tagSelectionEnabled;
const getIcon = (key: string) => {
switch (key) {
case "home":
return "home-outline";
case "settings":
return "cog-outline";
case "notebooks":
return "book-outline";
case "tags":
return "pound";
default:
return "home-outline";
}
};
return (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
backgroundColor: colors.primary.background,
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_SMALL
}}
>
{isSelectionEnabled ? (
<>
{[
{
title: "Select all",
icon: "check-all"
},
{
title: "Delete",
icon: "delete"
},
{
title: "Move",
icon: "arrow-right-bold-box-outline",
hidden: !notebookSelectionEnabled
},
{
title: "Close",
icon: "close"
}
].map((item) => (
<>
<Pressable
key={item.title}
onPress={async () => {
switch (item.title) {
case "Select all": {
if (notebookSelectionEnabled) {
useSideMenuNotebookSelectionStore
.getState()
.selectAll?.();
} else {
useSideMenuTagsSelectionStore.getState().selectAll?.();
}
break;
}
case "Delete": {
if (notebookSelectionEnabled) {
const ids = useSideMenuNotebookSelectionStore
.getState()
.getSelectedItemIds();
deleteItems(ids, "notebook");
} else {
const ids = useSideMenuTagsSelectionStore
.getState()
.getSelectedItemIds();
deleteItems(ids, "tag");
}
break;
}
case "Move": {
const ids = useSideMenuNotebookSelectionStore
.getState()
.getSelectedItemIds();
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
break;
}
case "Close": {
useSideMenuNotebookSelectionStore.setState({
enabled: false,
selection: {}
});
useSideMenuTagsSelectionStore.setState({
enabled: false,
selection: {}
});
break;
}
}
}}
style={{
borderRadius: 10,
paddingVertical: 2,
width: "25%"
}}
type="plain"
>
<Icon
name={item.icon}
color={colors.primary.icon}
size={SIZE.lg}
/>
<Paragraph
color={colors.secondary.paragraph}
size={SIZE.xxxs - 1}
>
{item.title}
</Paragraph>
</Pressable>
</>
))}
</>
) : (
<>
{props.navigationState.routes.map((route, index) => {
const isFocused = props.navigationState.index === index;
return route.key === "settings" ? (
<SettingsIcon
isFocused={isFocused}
jumpTo={props.jumpTo}
route={route}
/>
) : (
<Pressable
key={route.key}
onPress={() => {
props.jumpTo(route.key);
switch (route.key) {
case "notebooks":
Navigation.routeNeedsUpdate(
"Notebooks",
Navigation.routeUpdateFunctions.Notebooks
);
break;
case "tags":
Navigation.routeNeedsUpdate(
"Tags",
Navigation.routeUpdateFunctions.Tags
);
break;
default:
break;
}
}}
style={{
borderRadius: 10,
opacity: isFocused ? 1 : 0.6,
paddingVertical: 2,
width: "25%"
}}
type={isFocused ? "selected" : "plain"}
>
<Icon
name={getIcon(route.key)}
color={
isFocused ? colors.primary.accent : colors.primary.icon
}
size={SIZE.lg}
/>
<Paragraph
color={
isFocused
? colors.primary.paragraph
: colors.secondary.paragraph
}
size={SIZE.xxxs - 1}
>
{route.title}
</Paragraph>
</Pressable>
);
})}
</>
)}
</View>
);
};

View File

@@ -18,85 +18,146 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native"; import { db } from "../../common/database";
import { useTotalNotes } from "../../hooks/use-db-item";
import Navigation from "../../services/navigation"; import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store"; import { useFavoriteStore } from "../../stores/use-favorite-store";
import { SIZE, normalize } from "../../utils/size"; import useNavigationStore, {
import { Button } from "../ui/button"; RouteParams
} from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { useTrashStore } from "../../stores/use-trash-store";
import { SideMenuItem } from "../../utils/menu-items";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Pressable } from "../ui/pressable"; import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
function _MenuItem({ function _MenuItem({
item, item,
index, index,
testID, testID,
rightBtn renderIcon
}: { }: {
item: any; item: SideMenuItem;
index: number; index?: number;
testID: string; testID?: string;
rightBtn?: { renderIcon?: (item: SideMenuItem, size: number) => React.ReactNode;
name: string;
icon: string;
func: () => void;
};
}) { }) {
const [itemCount, setItemCount] = useState(0);
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const isFocused = useNavigationStore( const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.name (state) => state.focusedRouteId === item.id
); );
const primaryColors = isFocused ? colors.selected : colors.primary; const totalNotes = useTotalNotes(
item.dataType as "notebook" | "tag" | "color"
);
const getTotalNotesRef = useRef(totalNotes.getTotalNotes);
getTotalNotesRef.current = totalNotes.getTotalNotes;
const menuItemCount = !item.data
? itemCount
: totalNotes.totalNotes(item.data.id);
useEffect(() => {
let unsub: () => void;
if (!item.data) {
switch (item.id) {
case "Notes":
unsub = useNoteStore.subscribe((state) => {
setItemCount(
useNoteStore.getState().items?.placeholders?.length || 0
);
});
setItemCount(
useNoteStore.getState().items?.placeholders?.length || 0
);
break;
case "Favorites":
unsub = useFavoriteStore.subscribe((state) => {
setItemCount(state.items?.placeholders.length || 0);
});
setItemCount(
useFavoriteStore.getState().items?.placeholders?.length || 0
);
break;
case "Reminders":
unsub = useFavoriteStore.subscribe((state) => {
setItemCount(state.items?.placeholders.length || 0);
});
setItemCount(
useFavoriteStore.getState().items?.placeholders?.length || 0
);
break;
case "Monographs":
db.monographs.all.count().then((count) => {
setItemCount(count);
});
// TODO make it reactive?
break;
case "Trash":
unsub = useTrashStore.subscribe((state) => {
setItemCount(state.items?.placeholders.length || 0);
});
setItemCount(
useTrashStore.getState().items?.placeholders?.length || 0
);
break;
}
} else {
getTotalNotesRef.current?.([item.data.id]);
}
return () => {
unsub?.();
};
}, [item.data, item.id]);
const _onPress = () => { const _onPress = () => {
if (item.func) { if (item.onPress) return item.onPress(item);
item.func();
} else { if (useNavigationStore.getState().currentRoute !== item.id) {
if (useNavigationStore.getState().currentRoute !== item.name) { Navigation.navigate(item.id as keyof RouteParams, {
Navigation.navigate(item.name, { canGoBack: false
canGoBack: false,
beta: item.isBeta
}); });
} }
}
if (item.close) {
setImmediate(() => { setImmediate(() => {
Navigation.closeDrawer(); Navigation.closeDrawer();
}); });
}
}; };
return ( return (
<Pressable <Pressable
testID={testID} testID={testID}
key={item.name + index} key={item.id}
onPress={_onPress} onPress={_onPress}
onLongPress={() => item.onLongPress?.(item)}
type={isFocused ? "selected" : "plain"} type={isFocused ? "selected" : "plain"}
style={{ style={{
width: "100%", width: "100%",
alignSelf: "center", alignSelf: "center",
borderRadius: 5, borderRadius: 5,
flexDirection: "row", flexDirection: "row",
paddingHorizontal: 8, paddingHorizontal: DefaultAppStyles.GAP_SMALL,
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
height: normalize(50), paddingVertical: DefaultAppStyles.GAP_VERTICAL
marginBottom: 5
}} }}
> >
<View <View
style={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center" alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL
}} }}
> >
{renderIcon ? (
renderIcon(item, SIZE.md)
) : (
<Icon <Icon
style={{ style={{
width: 30,
textAlignVertical: "center", textAlignVertical: "center",
textAlign: "left" textAlign: "left"
}} }}
@@ -106,66 +167,42 @@ function _MenuItem({
item.icon === "crown" item.icon === "crown"
? colors.static.yellow ? colors.static.yellow
: isFocused : isFocused
? colors.selected.icon ? colors.selected.paragraph
: colors.secondary.icon : colors.secondary.icon
} }
size={SIZE.lg - 2} size={SIZE.md}
/> />
{isFocused ? (
<Heading color={colors.selected.heading} size={SIZE.md}>
{item.title || item.name}
</Heading>
) : (
<Paragraph size={SIZE.md}>{item.title || item.name}</Paragraph>
)} )}
{item.isBeta ? ( <Paragraph
<View color={
style={{ isFocused ? colors.primary.paragraph : colors.secondary.paragraph
borderRadius: 100, }
backgroundColor: primaryColors.accent, size={SIZE.sm}
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
> >
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}> {item.title}
{strings.beta()}
</Paragraph> </Paragraph>
</View> </View>
) : null}
</View>
{item.switch ? ( {menuItemCount > 0 ? (
<ToggleSwitch <Paragraph
isOn={item.on} size={SIZE.xxs}
onColor={primaryColors.accent} color={
offColor={primaryColors.icon} isFocused ? colors.primary.paragraph : colors.secondary.paragraph
size="small" }
animationSpeed={150} >
onToggle={_onPress} {menuItemCount}
/> </Paragraph>
) : rightBtn ? (
<Button
title={rightBtn.name}
type="shade"
height={30}
fontSize={SIZE.xs}
iconSize={SIZE.xs}
icon={rightBtn.icon}
style={{
borderRadius: 100,
paddingHorizontal: 16
}}
onPress={rightBtn.func}
/>
) : null} ) : null}
</Pressable> </Pressable>
); );
} }
export const MenuItem = React.memo(_MenuItem, (prev, next) => { export const MenuItem = React.memo(_MenuItem, (prev, next) => {
if (prev.item.name !== next.item.name) return false; if (
if (prev.rightBtn?.name !== next.rightBtn?.name) return false; prev.item.id !== next.item.id &&
prev.item.data?.dateModified !== next.item.data?.dateModified
)
return false;
return true; return true;
}); });

View File

@@ -18,30 +18,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Notebook, Tag } from "@notesnook/core"; import { Notebook, Tag } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme"; import React, { useEffect, useMemo } from "react";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database"; import { db } from "../../common/database";
import NotebookScreen from "../../screens/notebook"; import NotebookScreen from "../../screens/notebook";
import { TaggedNotes } from "../../screens/notes/tagged"; import { TaggedNotes } from "../../screens/notes/tagged";
import Navigation from "../../services/navigation"; import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store"; import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE, normalize } from "../../utils/size"; import { SideMenuItem } from "../../utils/menu-items";
import ReorderableList from "../list/reorderable-list"; import ReorderableList from "../list/reorderable-list";
import { Button } from "../ui/button"; import { MenuItem } from "./menu-item";
import { Notice } from "../ui/notice"; import { useThemeColors } from "@notesnook/theme";
import { Pressable } from "../ui/pressable"; import { DefaultAppStyles } from "../../utils/styles";
import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const PinnedSection = React.memo( export const PinnedSection = React.memo(
function PinnedSection() { function PinnedSection() {
const { colors } = useThemeColors();
const menuPins = useMenuStore((state) => state.menuPins); const menuPins = useMenuStore((state) => state.menuPins);
const loading = useSettingStore((state) => state.isAppLoading); const loading = useSettingStore((state) => state.isAppLoading);
const setMenuPins = useMenuStore((state) => state.setMenuPins); const setMenuPins = useMenuStore((state) => state.setMenuPins);
@@ -52,42 +45,55 @@ export const PinnedSection = React.memo(
} }
}, [loading, setMenuPins]); }, [loading, setMenuPins]);
const onPress = (item: Notebook | Tag) => { const onPress = React.useCallback((item: SideMenuItem) => {
if (item.type === "notebook") { const data = item.data as Notebook | Tag;
NotebookScreen.navigate(item); if (data.type === "notebook") {
} else if (item.type === "tag") { NotebookScreen.navigate(data);
TaggedNotes.navigate(item); } else if (data.type === "tag") {
TaggedNotes.navigate(data);
} }
setImmediate(() => { setImmediate(() => {
Navigation.closeDrawer(); Navigation.closeDrawer();
}); });
}; }, []);
const renderItem = ({
item, const menuItems = useMemo(
index () =>
}: { menuPins.map((item) => ({
item: Notebook | Tag; id: item.id,
index: number; title: item.title,
}) => { icon: item.type === "notebook" ? "notebook-outline" : "pound",
return <PinItem item={item} onPress={onPress} />; dataType: item.type,
}; data: item,
onPress: onPress
})) as SideMenuItem[],
[menuPins, onPress]
);
const renderItem = React.useCallback(({ item }: { item: SideMenuItem }) => {
return <MenuItem item={item} />;
}, []);
return ( return (
<View <View
style={{ style={{
flexGrow: 1 flexGrow: 1,
borderTopWidth: 1,
borderTopColor: colors.primary.border,
marginTop: DefaultAppStyles.GAP_SMALL,
paddingTop: DefaultAppStyles.GAP_SMALL
}} }}
> >
<ReorderableList <ReorderableList
onListOrderChanged={(data) => { onListOrderChanged={(data) => {
db.settings.setSideBarOrder("shortcuts", data); db.settings.setSideBarOrder("shortcuts", data);
}} }}
onHiddenItemsChanged={(data) => {}} onHiddenItemsChanged={() => {}}
canHideItems={false} canHideItems={false}
itemOrder={order} itemOrder={order}
hiddenItems={[]} hiddenItems={[]}
alwaysBounceVertical={false} alwaysBounceVertical={false}
data={menuPins} data={menuItems}
style={{ style={{
flexGrow: 1, flexGrow: 1,
width: "100%" width: "100%"
@@ -97,161 +103,9 @@ export const PinnedSection = React.memo(
}} }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderDraggableItem={renderItem} renderDraggableItem={renderItem}
ListEmptyComponent={
<Notice
size="small"
type="information"
text={strings.sideMenuNotice()}
style={{
marginHorizontal: 12
}}
/>
}
/> />
</View> </View>
); );
}, },
() => true () => true
); );
export const PinItem = React.memo(
function PinItem({
item,
onPress,
isPlaceholder
}: {
item: Notebook | Tag;
onPress: (item: Notebook | Tag) => void;
isPlaceholder?: boolean;
}) {
const { colors } = useThemeColors();
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const [visible, setVisible] = useState(false);
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.id
);
const primaryColors = isFocused ? colors.selected : colors.primary;
const color = isFocused ? colors.selected.accent : colors.primary.icon;
const fwdRef = useRef();
const icons = {
topic: "bookmark",
notebook: "book-outline",
tag: "pound"
};
return (
<>
{visible && (
<SheetWrapper
onClose={() => {
setVisible(false);
}}
gestureEnabled={false}
fwdRef={fwdRef}
>
<Seperator />
<Button
title={strings.removeShortcut()}
type="error"
onPress={async () => {
await db.shortcuts.remove(item.id);
setVisible(false);
setMenuPins();
}}
fontSize={SIZE.md}
width="95%"
style={{
marginBottom: 30
}}
/>
</SheetWrapper>
)}
<Pressable
type={isFocused ? "selected" : "plain"}
onPress={() => onPress(item)}
style={{
width: "100%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 8,
justifyContent: "space-between",
alignItems: "center",
height: normalize(50),
marginBottom: 5
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
flexGrow: 1,
flex: 1
}}
>
<View
style={{
width: 30,
justifyContent: "center"
}}
>
<Icon
allowFontScaling
color={color}
size={SIZE.lg - 2}
name={icons[item.type]}
/>
<Icon
style={{
position: "absolute",
bottom: -6,
left: -6
}}
allowFontScaling
color={color}
size={SIZE.xs}
name="arrow-top-right-thick"
/>
</View>
<View
style={{
alignItems: "flex-start",
flexGrow: 1,
flex: 1
}}
>
{isFocused ? (
<Heading
style={{
flexWrap: "wrap"
}}
color={primaryColors.heading}
size={SIZE.md}
>
{item.title}
</Heading>
) : (
<Paragraph
numberOfLines={1}
color={primaryColors.paragraph}
size={SIZE.md}
>
{item.title}
</Paragraph>
)}
</View>
</View>
</Pressable>
</>
);
},
(prev, next) => {
if (!next.item) return false;
if (prev.item.title !== next.item.title) return false;
if (prev.item?.dateModified !== next.item?.dateModified) return false;
if (prev.item?.id !== next.item?.id) return false;
return true;
}
);

View File

@@ -0,0 +1,112 @@
/*
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 { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { IconButton, IconButtonProps } from "../ui/icon-button";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
export const SideMenuHeader = (props: { rightButtons?: IconButtonProps[] }) => {
const { colors, isDark } = useThemeColors();
return (
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: DefaultAppStyles.GAP
}}
>
<View
style={{
flexDirection: "row",
gap: DefaultAppStyles.GAP_SMALL,
alignItems: "center"
}}
>
<View
style={{
backgroundColor: "black",
width: 28,
height: 28,
borderRadius: 10
}}
>
<SvgView
width={28}
height={28}
src={`<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_24_33)">
<path d="M1024 0H0V1024H1024V0Z" fill="black"/>
<path d="M724.985 682.919C707.73 733.33 673.15 775.984 627.397 803.291C581.645 830.598 527.687 840.787 475.128 832.044C422.568 823.301 374.814 796.194 340.365 755.546C305.916 714.898 287.006 663.347 287 610.064V499.814L366.121 532.867V610.019C366.114 630.798 370.555 651.337 379.145 670.256C387.735 689.176 400.276 706.037 415.925 719.707C418.895 722.294 421.978 724.814 425.161 727.166C448.518 744.554 476.563 754.518 505.655 755.763C506.645 755.763 507.601 755.842 508.58 755.864C509.559 755.887 510.83 755.864 511.955 755.864C513.08 755.864 514.205 755.864 515.33 755.864C516.455 755.864 517.265 755.864 518.255 755.763C547.336 754.515 575.371 744.56 598.726 727.188C601.899 724.837 604.981 722.328 607.963 719.741C628.519 701.761 643.619 678.375 651.545 652.241L724.985 682.919Z" fill="white"/>
<path d="M737 414V610.065C737 612.596 737 615.139 736.842 617.67L657.879 584.651V414C657.866 376.316 643.272 340.099 617.154 312.934C591.035 285.77 555.419 269.766 517.765 268.274C480.11 266.782 443.339 279.918 415.154 304.931C386.968 329.944 369.554 364.893 366.56 402.457C366.279 406.26 366.121 410.119 366.121 414V462.712L287 429.637V189H512C571.674 189 628.903 212.705 671.099 254.901C713.295 297.097 737 354.326 737 414Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_24_33">
<rect width="1024" height="1024" rx="200" fill="white"/>
</clipPath>
</defs>
</svg>
`}
/>
</View>
<Heading size={SIZE.lg}>Notesnook</Heading>
</View>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL
}}
>
{props.rightButtons?.map((button, index) => (
<IconButton
key={index}
{...button}
style={{
width: 28,
height: 28
}}
color={colors.primary.icon}
size={SIZE.lg}
/>
))}
<IconButton
onPress={() => {
useThemeStore.getState().setColorScheme();
}}
style={{
width: 28,
height: 28
}}
color={colors.primary.icon}
name={isDark ? "weather-night" : "weather-sunny"}
size={SIZE.lg}
/>
</View>
</View>
);
};

View File

@@ -0,0 +1,105 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { MenuItemsList } from "../../utils/menu-items";
import { DefaultAppStyles } from "../../utils/styles";
import ReorderableList from "../list/reorderable-list";
import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { SideMenuHeader } from "./side-menu-header";
export function SideMenuHome() {
const { colors } = useThemeColors();
const [isAppLoading, introCompleted] = useSettingStore((state) => [
state.isAppLoading,
state.settings.introCompleted
]);
const [order, hiddensItems] = useMenuStore((state) => [
state.order["routes"],
state.hiddenItems["routes"]
]);
return (
<View
style={{
height: "100%",
width: "100%",
paddingTop: DefaultAppStyles.GAP_SMALL,
backgroundColor: colors.primary.background,
gap: DefaultAppStyles.GAP,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<SideMenuHeader />
{!isAppLoading && introCompleted ? (
<DraxProvider>
<DraxScrollView nestedScrollEnabled={false}>
<ReorderableList
onListOrderChanged={(data) => {
db.settings.setSideBarOrder("routes", data);
}}
onHiddenItemsChanged={(data) => {
db.settings.setSideBarHiddenItems("routes", data);
}}
itemOrder={order}
hiddenItems={hiddensItems}
alwaysBounceVertical={false}
data={MenuItemsList}
style={{
width: "100%"
}}
contentContainerStyle={{
gap: 2
}}
showsVerticalScrollIndicator={false}
renderDraggableItem={({ item, index }) => {
return (
<MenuItem
key={item.title}
item={{
...item,
title:
strings.routes[
item.title as keyof typeof strings.routes
]?.() || item.title
}}
testID={item.title}
index={index}
/>
);
}}
/>
<ColorSection />
<PinnedSection />
</DraxScrollView>
</DraxProvider>
) : null}
</View>
);
}

View File

@@ -0,0 +1,450 @@
/*
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 { Notebook } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { FlatList, ListRenderItemInfo, View } from "react-native";
import { UseBoundStore } from "zustand";
import { db } from "../../common/database";
import { useTotalNotes } from "../../hooks/use-db-item";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import NotebookScreen from "../../screens/notebook";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { TreeItem } from "../../stores/create-notebook-tree-stores";
import { SelectionStore } from "../../stores/item-selection-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotebooks } from "../../stores/use-notebook-store";
import { eOnNotebookUpdated } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Properties } from "../properties";
import { AddNotebookSheet } from "../sheets/add-notebook";
import AppIcon from "../ui/AppIcon";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { SideMenuHeader } from "./side-menu-header";
import {
useSideMenuNotebookExpandedStore,
useSideMenuNotebookSelectionStore,
useSideMenuNotebookTreeStore
} from "./stores";
const NotebookItem = ({
index,
item,
expanded,
selected,
onToggleExpanded,
focused,
selectionEnabled,
selectionStore,
onItemUpdate,
onPress,
onLongPress
}: {
index: number;
item: TreeItem;
expanded?: boolean;
onToggleExpanded?: () => void;
selected?: boolean;
focused?: boolean;
selectionEnabled?: boolean;
selectionStore: UseBoundStore<SelectionStore>;
onItemUpdate: (id?: string) => void;
onPress?: () => void;
onLongPress?: () => void;
}) => {
const notebook = item.notebook;
const [nestedNotebooksSelected, setNestedNotebooksSelected] =
React.useState(false);
const isFocused = focused;
const { totalNotes, getTotalNotes } = useTotalNotes("notebook");
const getTotalNotesRef = React.useRef(getTotalNotes);
getTotalNotesRef.current = getTotalNotes;
const { colors } = useThemeColors("sheet");
useEffect(() => {
getTotalNotesRef.current([item.notebook.id]);
}, [item.notebook]);
useEffect(() => {
if (selectionEnabled) {
const selector = db.relations.from(
{
type: "notebook",
id: item.notebook.id
},
"notebook"
).selector;
selector.ids().then((ids) => {
setNestedNotebooksSelected(
ids.length === 0
? true
: ids.every(
(id) => selectionStore.getState().selection[id] === "selected"
)
);
});
}
}, [selected, item.notebook.id, selectionEnabled, selectionStore]);
async function selectAll() {
const selector = db.relations.from(
{
type: "notebook",
id: item.notebook.id
},
"notebook"
).selector;
const ids = await selector.ids();
selectionStore.setState({
selection: {
...selectionStore.getState().selection,
...ids.reduce((acc: any, id) => {
acc[id] = "selected";
return acc;
}, {})
}
});
setNestedNotebooksSelected(true);
}
async function deselectAll() {
const selector = db.relations.from(
{
type: "notebook",
id: item.notebook.id
},
"notebook"
).selector;
const ids = await selector.ids();
useSideMenuNotebookSelectionStore.setState({
selection: {
...selectionStore.getState().selection,
...ids.reduce((acc: any, id) => {
acc[id] = "deselected";
return acc;
}, {})
}
});
setNestedNotebooksSelected(false);
}
useEffect(() => {
const unsub = selectionStore.subscribe((state) => {
if (state.enabled) {
const selector = db.relations.from(
{
type: "notebook",
id: item.notebook.id
},
"notebook"
).selector;
selector.ids().then((ids) => {
if (!ids.length) return;
setNestedNotebooksSelected(
ids.length === 0
? true
: ids.every(
(id) => selectionStore.getState().selection[id] === "selected"
)
);
});
}
});
return () => {
unsub();
};
}, [item.notebook.id, selectionStore]);
useEffect(() => {
const onNotebookUpdate = (id?: string) => {
if (id && id !== notebook.id) return;
onItemUpdate(id);
};
eSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
return () => {
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
};
}, [notebook.id, onItemUpdate]);
return (
<View
style={{
paddingLeft: item.depth > 0 && item.depth < 6 ? 15 : undefined,
width: "100%",
marginTop: 2
}}
>
<Pressable
type={isFocused ? "selected" : "transparent"}
onLongPress={() => {
onLongPress?.();
}}
testID={`notebook-sheet-item-${item.depth}-${index}`}
onPress={async () => {
if (selectionEnabled) {
if (selected && !nestedNotebooksSelected) {
console.log("Select all...");
return selectAll();
}
await deselectAll();
selectionStore
.getState()
.markAs(item.notebook, selected ? "deselected" : "selected");
if (selectionStore.getState().getSelectedItemIds().length === 0) {
selectionStore.setState({
enabled: false
});
}
} else {
onPress?.();
}
}}
style={{
justifyContent: "space-between",
width: "100%",
alignItems: "center",
flexDirection: "row",
borderRadius: 5,
paddingRight: DefaultAppStyles.GAP_SMALL
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<IconButton
size={SIZE.md}
color={selected ? colors.selected.icon : colors.primary.icon}
onPress={() => {
onToggleExpanded?.();
}}
top={0}
left={0}
bottom={0}
right={0}
style={{
width: 32,
height: 32,
borderRadius: 5
}}
name={expanded ? "chevron-down" : "chevron-right"}
/>
<Paragraph
color={
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
}
size={SIZE.xs}
>
{notebook?.title}
</Paragraph>
</View>
{selectionEnabled ? (
<View
style={{
width: 22,
height: 22,
justifyContent: "center",
alignItems: "center"
}}
>
<AppIcon
name={
selected
? !nestedNotebooksSelected
? "checkbox-intermediate"
: "checkbox-outline"
: "checkbox-blank-outline"
}
color={selected ? colors.selected.icon : colors.primary.icon}
/>
</View>
) : (
<>
{totalNotes(notebook?.id) ? (
<Paragraph size={SIZE.xxs} color={colors.secondary.paragraph}>
{totalNotes?.(notebook?.id)}
</Paragraph>
) : null}
</>
)}
</Pressable>
</View>
);
};
export const SideMenuNotebooks = () => {
const tree = useSideMenuNotebookTreeStore((state) => state.tree);
const [notebooks, loading] = useNotebooks();
const insets = useGlobalSafeAreaInsets();
const loadRootNotebooks = React.useCallback(async () => {
if (!notebooks) return;
const _notebooks: Notebook[] = [];
for (let i = 0; i < notebooks.placeholders.length; i++) {
_notebooks[i] = (await notebooks?.item(i))?.item as Notebook;
}
useSideMenuNotebookTreeStore.getState().addNotebooks("root", _notebooks, 0);
}, [notebooks]);
useEffect(() => {
(async () => {
if (!loading) {
loadRootNotebooks();
}
})();
}, [loadRootNotebooks, loading]);
useEffect(() => {
useSideMenuNotebookSelectionStore.setState({
selectAll: async () => {
const allNotebooks = await db.notebooks.all.items();
const allSelected = allNotebooks.every((notebook) => {
return (
useSideMenuNotebookSelectionStore.getState().selection[
notebook.id
] === "selected"
);
});
if (allSelected) {
useSideMenuNotebookSelectionStore.setState({
selection: {}
});
return;
}
useSideMenuNotebookSelectionStore.setState({
selection: allNotebooks.reduce((acc: any, item) => {
acc[item.id] = "selected";
return acc;
}, {})
});
}
});
}, []);
const renderItem = React.useCallback((info: ListRenderItemInfo<TreeItem>) => {
return <NotebookItemWrapper index={info.index} item={info.item} />;
}, []);
return (
<FlatList
style={{
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: DefaultAppStyles.GAP_SMALL
}}
data={tree}
keyExtractor={(item) => item.notebook.id}
windowSize={3}
ListHeaderComponent={
<SideMenuHeader
rightButtons={[
{
name: "plus",
onPress: () => {
AddNotebookSheet.present();
}
}
]}
/>
}
stickyHeaderIndices={[0]}
renderItem={renderItem}
/>
);
};
const NotebookItemWrapper = ({
item,
index
}: {
item: TreeItem;
index: number;
}) => {
const expanded = useSideMenuNotebookExpandedStore(
(state) => state.expanded[item.notebook.id]
);
const selectionEnabled = useSideMenuNotebookSelectionStore(
(state) => state.enabled
);
const selected = useSideMenuNotebookSelectionStore(
(state) => state.selection[item.notebook.id] === "selected"
);
const focused = useNavigationStore(
(state) => state.focusedRouteId === item.notebook.id
);
useEffect(() => {
if (expanded) {
useSideMenuNotebookTreeStore
.getState()
.fetchAndAdd(item.notebook.id, item.depth + 1);
} else {
useSideMenuNotebookTreeStore.getState().removeChildren(item.notebook.id);
}
}, [expanded, item.depth, item.notebook]);
const onItemUpdate = React.useCallback(async () => {
const notebook = await db.notebooks.notebook(item.notebook.id);
if (notebook) {
useSideMenuNotebookTreeStore
.getState()
.updateItem(item.notebook.id, notebook);
} else {
useSideMenuNotebookTreeStore.getState().removeItem(item.notebook.id);
}
}, [item.notebook.id]);
return (
<NotebookItem
item={item}
index={index}
expanded={expanded}
onToggleExpanded={() => {
useSideMenuNotebookExpandedStore
.getState()
.setExpanded(item.notebook.id);
}}
selected={selected}
selectionEnabled={selectionEnabled}
selectionStore={useSideMenuNotebookSelectionStore}
onItemUpdate={onItemUpdate}
focused={focused}
onPress={() => {
NotebookScreen.navigate(item.notebook, false);
}}
onLongPress={() => {
Properties.present(item.notebook, false);
}}
/>
);
};

View File

@@ -0,0 +1,207 @@
/*
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 { Tag, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { db } from "../../common/database";
import { useDBItem, useTotalNotes } from "../../hooks/use-db-item";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { TaggedNotes } from "../../screens/notes/tagged";
import useNavigationStore from "../../stores/use-navigation-store";
import { useTags } from "../../stores/use-tag-store";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Properties } from "../properties";
import AppIcon from "../ui/AppIcon";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { SideMenuHeader } from "./side-menu-header";
import { useSideMenuTagsSelectionStore } from "./stores";
const TagItem = (props: {
tags: VirtualizedGrouping<Tag>;
id: number | string;
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(props.id, "tag", props.tags);
const isSelected = useSideMenuTagsSelectionStore((state) =>
item?.id ? state.selection[item.id] === "selected" : false
);
const enabled = useSideMenuTagsSelectionStore((state) => state.enabled);
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item?.id
);
const totalNotes = useTotalNotes("tag");
const totalNotesRef = React.useRef(totalNotes);
totalNotesRef.current = totalNotes;
useEffect(() => {
if (item?.id) {
totalNotesRef.current?.getTotalNotes([item?.id]);
}
}, [item]);
return item ? (
<Pressable
type={isSelected || isFocused ? "selected" : "transparent"}
onLongPress={() => {
Properties.present(item);
}}
testID={`tag-item-${props.id}`}
onPress={() => {
if (enabled) {
useSideMenuTagsSelectionStore
.getState()
.markAs(item, isSelected ? "deselected" : "selected");
if (
useSideMenuTagsSelectionStore.getState().getSelectedItemIds()
.length === 0
) {
useSideMenuTagsSelectionStore.setState({
enabled: false
});
}
} else {
TaggedNotes.navigate(item, false);
}
}}
style={{
justifyContent: "space-between",
width: "100%",
alignItems: "center",
flexDirection: "row",
borderRadius: 5,
paddingRight: DefaultAppStyles.GAP_SMALL
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
width: 32,
height: 32,
justifyContent: "center",
alignItems: "center"
}}
>
<AppIcon
size={SIZE.md}
color={isFocused ? colors.selected.icon : colors.secondary.icon}
name="pound"
/>
</View>
<Paragraph
color={
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
}
size={SIZE.xs}
>
{item?.title}
</Paragraph>
</View>
{enabled ? (
<View
style={{
width: 22,
height: 22,
justifyContent: "center",
alignItems: "center"
}}
>
<AppIcon
name={isSelected ? "checkbox-outline" : "checkbox-blank-outline"}
color={isSelected ? colors.selected.icon : colors.primary.icon}
/>
</View>
) : (
<>
{item?.id && totalNotes.totalNotes?.(item?.id) ? (
<Paragraph size={SIZE.xxs} color={colors.secondary.paragraph}>
{totalNotes.totalNotes(item?.id)}
</Paragraph>
) : null}
</>
)}
</Pressable>
) : null;
};
export const SideMenuTags = () => {
const [tags] = useTags();
const insets = useGlobalSafeAreaInsets();
useEffect(() => {
useSideMenuTagsSelectionStore.setState({
selectAll: async () => {
const tags = await db.tags.all.items();
const allSelected = tags.every((tag) => {
return (
useSideMenuTagsSelectionStore.getState().selection[tag.id] ===
"selected"
);
});
if (allSelected) {
useSideMenuTagsSelectionStore.getState().setSelection({});
} else {
useSideMenuTagsSelectionStore.getState().setSelection(
tags.reduce((acc: any, tag) => {
acc[tag.id] = "selected";
return acc;
}, {})
);
}
}
});
}, []);
const renderItem = React.useCallback(
(info: { index: number }) => {
return <TagItem id={info.index} tags={tags!} />;
},
[tags]
);
return (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: DefaultAppStyles.GAP_SMALL,
width: "100%",
height: "100%"
}}
>
<SideMenuHeader />
<FlashList
data={tags?.placeholders}
estimatedItemSize={32}
renderItem={renderItem}
/>
</View>
);
};

View File

@@ -0,0 +1,32 @@
/*
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 { createNotebookTreeStores } from "../../stores/create-notebook-tree-stores";
import { createItemSelectionStore } from "../../stores/item-selection-store";
export const {
useSideMenuNotebookExpandedStore,
useSideMenuNotebookSelectionStore,
useSideMenuNotebookTreeStore
} = createNotebookTreeStores(true, false);
export const useSideMenuTagsSelectionStore = createItemSelectionStore(
true,
false
);

View File

@@ -17,6 +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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo"; import { useNetInfo } from "@react-native-community/netinfo";
import React from "react"; import React from "react";
@@ -30,13 +31,12 @@ import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store"; import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store"; import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { eOpenLoginDialog } from "../../utils/events"; import { eOpenLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable"; import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since"; import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const UserStatus = () => { export const UserStatus = () => {
const { colors, isDark } = useThemeColors(); const { colors, isDark } = useThemeColors();
@@ -71,7 +71,7 @@ export const UserStatus = () => {
<Pressable <Pressable
onPress={() => { onPress={() => {
Navigation.navigate("Settings"); Navigation.navigate("Settings");
tabBarRef.current.closeDrawer(); fluidTabsRef.current.closeDrawer();
}} }}
type="plain" type="plain"
style={{ style={{
@@ -206,7 +206,7 @@ export const UserStatus = () => {
if (user) { if (user) {
Sync.run(); Sync.run();
} else { } else {
tabBarRef.current?.closeDrawer(); fluidTabsRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog); eSendEvent(eOpenLoginDialog);
} }
}} }}

View File

@@ -25,7 +25,7 @@ import { RGB_Linear_Shade, hexToRGBA } from "../../../utils/colors";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import NativeTooltip from "../../../utils/tooltip"; import NativeTooltip from "../../../utils/tooltip";
import { Pressable, PressableProps } from "../pressable"; import { Pressable, PressableProps } from "../pressable";
interface IconButtonProps extends PressableProps { export interface IconButtonProps extends PressableProps {
name: string; name: string;
color?: ColorValue; color?: ColorValue;
size?: number; size?: number;

View File

@@ -273,13 +273,12 @@ export const Pressable = ({
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
marginBottom: 0, marginBottom: 0,
borderColor: borderColor: pressed
pressed && !disabled
? customSelectedColor ? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false) ? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor : borderSelectedColor || borderColor
: borderColor || "transparent", : borderColor || "transparent",
borderWidth: borderWidth borderWidth: noborder ? 0 : borderWidth
}, },
style, style,
{ {

View File

@@ -18,20 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* eslint-disable no-inner-declarations */ /* eslint-disable no-inner-declarations */
import { import {
Color, Item,
createInternalLink,
ItemReference, ItemReference,
Note, Note,
Notebook, VAULT_ERRORS,
Reminder, createInternalLink
Tag,
TrashItem,
VAULT_ERRORS
} from "@notesnook/core"; } from "@notesnook/core";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { DisplayedNotification } from "@notifee/react-native"; import { DisplayedNotification } from "@notifee/react-native";
import Clipboard from "@react-native-clipboard/clipboard"; import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { InteractionManager, Platform } from "react-native"; import { InteractionManager, Platform } from "react-native";
import Share from "react-native-share"; import Share from "react-native-share";
import { DatabaseLogger, db } from "../common/database"; import { DatabaseLogger, db } from "../common/database";
@@ -48,6 +45,11 @@ import { ReferencesList } from "../components/sheets/references";
import { RelationsList } from "../components/sheets/relations-list/index"; import { RelationsList } from "../components/sheets/relations-list/index";
import ReminderSheet from "../components/sheets/reminder"; import ReminderSheet from "../components/sheets/reminder";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store"; import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import {
useSideMenuNotebookSelectionStore,
useSideMenuTagsSelectionStore
} from "../components/side-menu/stores";
import { ButtonProps } from "../components/ui/button";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store"; import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import { import {
eSendEvent, eSendEvent,
@@ -69,13 +71,83 @@ import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text"; import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
export type ActionId =
| "select"
| "restore"
| "delete"
| "reorder"
| "rename-tag"
| "rename-color"
| "pin"
| "add-shortcut"
| "rename-notebook"
| "add-notebook"
| "edit-notebook"
| "default-notebook"
| "move-notes"
| "move-notebook"
| "disable-reminder"
| "edit-reminder"
| "delete-reminder"
| "delete"
| "delete-trash"
| "add-reminder"
| "copy"
| "share"
| "read-only"
| "local-only"
| "duplicate"
| "add-note"
| "attachments"
| "history"
| "copy-link"
| "reminders"
| "lock-unlock"
| "publish"
| "export"
| "notebooks"
| "add-tag"
| "references"
| "pin-to-notifications"
| "favorite"
| "remove-from-notebook"
| "trash";
export type Action = {
id: ActionId;
title: string;
icon: string;
onPress: () => void;
isToggle?: boolean;
checked?: boolean;
pro?: boolean;
hidden?: boolean;
activeColor?: string;
type?: ButtonProps["type"];
};
function isNotePinnedInNotifications(item: Item) {
const pinned = Notifications.getPinnedNotes();
if (!pinned || pinned.length === 0) {
return undefined;
}
const index = pinned.findIndex((notif) => notif.id === item.id);
if (index !== -1) {
return pinned[index];
}
return undefined;
}
export const useActions = ({ export const useActions = ({
close, close,
item item,
customActionHandlers
}: { }: {
item: Note | Notebook | Reminder | Tag | Color | TrashItem; item: Item;
close: () => void; close: () => void;
customActionHandlers?: Record<ActionId, () => void>;
}) => { }) => {
const { colors } = useThemeColors();
const setMenuPins = useMenuStore((state) => state.setMenuPins); const setMenuPins = useMenuStore((state) => state.setMenuPins);
const [isPinnedToMenu, setIsPinnedToMenu] = useState( const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(item.id) db.shortcuts.exists(item.id)
@@ -83,15 +155,14 @@ export const useActions = ({
const processingId = useRef<"shareNote" | "copyContent">(); const processingId = useRef<"shareNote" | "copyContent">();
const user = useUserStore((state) => state.user); const user = useUserStore((state) => state.user);
const [notifPinned, setNotifPinned] = useState<DisplayedNotification>(); const [notifPinned, setNotifPinned] = useState<DisplayedNotification>();
const [defaultNotebook, setDefaultNotebook] = useState( const [defaultNotebook, setDefaultNotebook] = useState(
db.settings.getDefaultNotebook() db.settings.getDefaultNotebook()
); );
const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false); const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false);
const [locked, setLocked] = useState(false);
const isPublished = const isPublished =
item.type === "note" && db.monographs.isPublished(item.id); item.type === "note" && db.monographs.isPublished(item.id);
const [locked, setLocked] = useState(false);
useEffect(() => { useEffect(() => {
if (item.type === "note") { if (item.type === "note") {
@@ -99,46 +170,41 @@ export const useActions = ({
} }
}, [item]); }, [item]);
const checkNotifPinned = useCallback(() => {
const pinned = Notifications.getPinnedNotes();
if (!pinned || pinned.length === 0) {
setNotifPinned(undefined);
return;
}
const index = pinned.findIndex((notif) => notif.id === item.id);
if (index !== -1) {
setNotifPinned(pinned[index]);
} else {
setNotifPinned(undefined);
}
}, [item.id]);
useEffect(() => { useEffect(() => {
if (item.type !== "note") return; if (item.type !== "note") return;
checkNotifPinned(); setNotifPinned(isNotePinnedInNotifications(item));
setIsPinnedToMenu(db.shortcuts.exists(item.id)); setIsPinnedToMenu(db.shortcuts.exists(item.id));
}, [checkNotifPinned, item]); }, [item]);
const onUpdate = useCallback( useEffect(() => {
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
if (item.type !== "note" || currentRoute !== "Notebook" || !focusedRouteId)
return;
!!db.relations
.to(item, "notebook")
.selector.find((v) => v("id", "==", focusedRouteId))
.then((notebook) => {
setNoteInCurrentNotebook(!!notebook);
});
}, [item]);
useEffect(() => {
const sub = eSubscribeEvent(
Notifications.Events.onUpdate,
async (type: string) => { async (type: string) => {
if (type === "unpin") { if (type === "unpin") {
await Notifications.get(); await Notifications.get();
checkNotifPinned(); setNotifPinned(isNotePinnedInNotifications(item));
}
} }
},
[checkNotifPinned]
); );
useEffect(() => {
const sub = eSubscribeEvent(Notifications.Events.onUpdate, onUpdate);
return () => { return () => {
sub?.unsubscribe(); sub?.unsubscribe();
}; };
}, [item, onUpdate]); }, [item]);
async function restoreTrashItem() { async function restoreTrashItem() {
if (!checkItemSynced()) return;
close(); close();
if ((await db.trash.restore(item.id)) === false) return; if ((await db.trash.restore(item.id)) === false) return;
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
@@ -154,20 +220,16 @@ export const useActions = ({
async function pinItem() { async function pinItem() {
if (!item.id) return; if (!item.id) return;
close();
if (item.type === "note") { if (item.type === "note") {
await db.notes.pin(!item?.pinned, item.id); await db.notes.pin(!item?.pinned, item.id);
} else if (item.type === "notebook") { } else if (item.type === "notebook") {
await db.notebooks.pin(!item?.pinned, item.id); await db.notebooks.pin(!item?.pinned, item.id);
} }
close();
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
} }
const checkItemSynced = () => {
return true;
};
async function createMenuShortcut() { async function createMenuShortcut() {
if (item.type !== "notebook" && item.type !== "tag") return; if (item.type !== "notebook" && item.type !== "tag") return;
@@ -299,7 +361,6 @@ export const useActions = ({
async function deleteTrashItem() { async function deleteTrashItem() {
if (item.type !== "trash") return; if (item.type !== "trash") return;
if (!checkItemSynced()) return;
close(); close();
await sleep(300); await sleep(300);
presentDialog({ presentDialog({
@@ -323,42 +384,14 @@ export const useActions = ({
}); });
} }
const actions: { const actions: Action[] = [];
id: string;
title: string;
icon: string;
func: () => void;
close?: boolean;
check?: boolean;
on?: boolean;
pro?: boolean;
switch?: boolean;
hidden?: boolean;
type?: string;
color?: string;
}[] = [
// {
// id: "ReferencedIn",
// title: "References",
// icon: "link",
// func: async () => {
// close();
// RelationsList.present({
// reference: item,
// referenceType: "note",
// title: "Referenced in",
// relationType: "to",
// });
// }
// }
];
if (item.type === "tag") { if (item.type === "tag") {
actions.push({ actions.push({
id: "rename-tag", id: "rename-tag",
title: strings.rename(), title: strings.rename(),
icon: "square-edit-outline", icon: "square-edit-outline",
func: renameTag onPress: renameTag
}); });
} }
@@ -367,14 +400,14 @@ export const useActions = ({
id: "rename-color", id: "rename-color",
title: strings.rename(), title: strings.rename(),
icon: "square-edit-outline", icon: "square-edit-outline",
func: renameColor onPress: renameColor
}); });
actions.push({ actions.push({
id: "reorder", id: "reorder",
title: strings.reorder(), title: strings.reorder(),
icon: "sort-ascending", icon: "sort-ascending",
func: () => { onPress: () => {
useSideBarDraggingStore.setState({ useSideBarDraggingStore.setState({
dragging: true dragging: true
}); });
@@ -391,7 +424,7 @@ export const useActions = ({
? strings.turnOffReminder() ? strings.turnOffReminder()
: strings.turnOnReminder(), : strings.turnOnReminder(),
icon: !item.disabled ? "bell-off-outline" : "bell", icon: !item.disabled ? "bell-off-outline" : "bell",
func: async () => { onPress: async () => {
close(); close();
await db.reminders.add({ await db.reminders.add({
...item, ...item,
@@ -406,10 +439,9 @@ export const useActions = ({
id: "edit-reminder", id: "edit-reminder",
title: strings.editReminder(), title: strings.editReminder(),
icon: "pencil", icon: "pencil",
func: async () => { onPress: async () => {
ReminderSheet.present(item); ReminderSheet.present(item);
}, }
close: false
} }
); );
} }
@@ -420,13 +452,13 @@ export const useActions = ({
id: "restore", id: "restore",
title: strings.restore(), title: strings.restore(),
icon: "delete-restore", icon: "delete-restore",
func: restoreTrashItem onPress: restoreTrashItem
}, },
{ {
id: "delete", id: "delete",
title: strings.delete(), title: strings.delete(),
icon: "delete", icon: "delete",
func: deleteTrashItem onPress: deleteTrashItem
} }
); );
} }
@@ -436,11 +468,27 @@ export const useActions = ({
id: "add-shortcut", id: "add-shortcut",
title: isPinnedToMenu ? strings.removeShortcut() : strings.addShortcut(), title: isPinnedToMenu ? strings.removeShortcut() : strings.addShortcut(),
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant", icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
func: createMenuShortcut, onPress: createMenuShortcut,
close: false, isToggle: true,
check: true, checked: isPinnedToMenu,
on: isPinnedToMenu, activeColor: colors.error.paragraph
pro: true });
actions.push({
id: "select",
title: strings.select() + " " + strings.dataTypes[item.type](),
icon: "checkbox-outline",
onPress: () => {
const store =
item.type === "tag"
? useSideMenuTagsSelectionStore
: useSideMenuNotebookSelectionStore;
store.setState({
enabled: true,
selection: {}
});
store.getState().markAs(item, "selected");
close();
}
}); });
} }
@@ -450,7 +498,7 @@ export const useActions = ({
id: "add-notebook", id: "add-notebook",
title: strings.addNotebook(), title: strings.addNotebook(),
icon: "plus", icon: "plus",
func: async () => { onPress: async () => {
AddNotebookSheet.present(undefined, item); AddNotebookSheet.present(undefined, item);
} }
}, },
@@ -458,7 +506,7 @@ export const useActions = ({
id: "edit-notebook", id: "edit-notebook",
title: strings.editNotebook(), title: strings.editNotebook(),
icon: "square-edit-outline", icon: "square-edit-outline",
func: async () => { onPress: async () => {
AddNotebookSheet.present(item); AddNotebookSheet.present(item);
} }
}, },
@@ -470,7 +518,7 @@ export const useActions = ({
: strings.setAsDefault(), : strings.setAsDefault(),
hidden: item.type !== "notebook", hidden: item.type !== "notebook",
icon: "notebook", icon: "notebook",
func: async () => { onPress: async () => {
if (defaultNotebook === item.id) { if (defaultNotebook === item.id) {
await db.settings.setDefaultNotebook(undefined); await db.settings.setDefaultNotebook(undefined);
setDefaultNotebook(undefined); setDefaultNotebook(undefined);
@@ -483,14 +531,14 @@ export const useActions = ({
} }
close(); close();
}, },
on: defaultNotebook === item.id checked: defaultNotebook === item.id
}, },
{ {
id: "move-notes", id: "move-notes",
title: strings.moveNotes(), title: strings.moveNotes(),
hidden: item.type !== "notebook", hidden: item.type !== "notebook",
icon: "text", icon: "text",
func: () => { onPress: () => {
MoveNotes.present(item); MoveNotes.present(item);
} }
}, },
@@ -498,7 +546,7 @@ export const useActions = ({
id: "move-notebook", id: "move-notebook",
title: strings.moveNotebookFix(), title: strings.moveNotebookFix(),
icon: "arrow-right-bold-box-outline", icon: "arrow-right-bold-box-outline",
func: () => { onPress: () => {
MoveNotebookSheet.present([item]); MoveNotebookSheet.present([item]);
} }
} }
@@ -510,10 +558,9 @@ export const useActions = ({
id: "pin", id: "pin",
title: item.pinned ? strings.unpin() : strings.pin(), title: item.pinned ? strings.unpin() : strings.pin(),
icon: item.pinned ? "pin-off-outline" : "pin-outline", icon: item.pinned ? "pin-off-outline" : "pin-outline",
func: pinItem, onPress: pinItem,
close: false, isToggle: true,
check: true, checked: item.pinned,
on: item.pinned,
pro: true pro: true
}); });
} }
@@ -535,7 +582,7 @@ export const useActions = ({
} }
async function toggleLocalOnly() { async function toggleLocalOnly() {
if (!checkItemSynced() || !user) return; if (!user) return;
await db.notes.localOnly(!(item as Note).localOnly, item?.id); await db.notes.localOnly(!(item as Note).localOnly, item?.id);
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
close(); close();
@@ -556,7 +603,6 @@ export const useActions = ({
}; };
const duplicateNote = async () => { const duplicateNote = async () => {
if (!checkItemSynced()) return;
await db.notes.duplicate(item.id); await db.notes.duplicate(item.id);
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
close(); close();
@@ -577,19 +623,16 @@ export const useActions = ({
async function addToFavorites() { async function addToFavorites() {
if (!item.id || item.type !== "note") return; if (!item.id || item.type !== "note") return;
close();
await db.notes.favorite(!item.favorite, item.id); await db.notes.favorite(!item.favorite, item.id);
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
close();
} }
async function pinToNotifications() { async function pinToNotifications() {
if (!checkItemSynced()) return;
if (Platform.OS === "ios") return;
if (notifPinned) { if (notifPinned) {
Notifications.remove(item.id); Notifications.remove(item.id);
await Notifications.get(); await Notifications.get();
checkNotifPinned(); setNotifPinned(isNotePinnedInNotifications(item));
return; return;
} }
if (locked) { if (locked) {
@@ -603,7 +646,7 @@ export const useActions = ({
const text = await convertNoteToText(item as Note, true); const text = await convertNoteToText(item as Note, true);
const html = (text || "").replace(/\n/g, "<br />"); const html = (text || "").replace(/\n/g, "<br />");
await Notifications.displayNotification({ await Notifications.displayNotification({
title: item.title, title: (item as Note).title,
message: (item as Note).headline || text || "", message: (item as Note).headline || text || "",
subtitle: "", subtitle: "",
bigText: html, bigText: html,
@@ -612,11 +655,10 @@ export const useActions = ({
id: item.id id: item.id
}); });
await Notifications.get(); await Notifications.get();
checkNotifPinned(); setNotifPinned(isNotePinnedInNotifications(item));
} }
async function publishNote() { async function publishNote() {
if (!checkItemSynced()) return;
if (!user) { if (!user) {
ToastManager.show({ ToastManager.show({
heading: strings.loginRequired(), heading: strings.loginRequired(),
@@ -657,7 +699,6 @@ export const useActions = ({
}); });
return; return;
} }
if (!checkItemSynced()) return;
if (locked) { if (locked) {
close(); close();
await sleep(300); await sleep(300);
@@ -687,8 +728,6 @@ export const useActions = ({
async function addToVault() { async function addToVault() {
if (item.type !== "note") return; if (item.type !== "note") return;
if (!checkItemSynced()) return;
if (locked) { if (locked) {
close(); close();
await sleep(300); await sleep(300);
@@ -709,10 +748,10 @@ export const useActions = ({
Navigation.queueRoutesForUpdate(); Navigation.queueRoutesForUpdate();
eSendEvent(eUpdateNoteInEditor, item, true); eSendEvent(eUpdateNoteInEditor, item, true);
} }
} catch (e: any) { } catch (e: unknown) {
close(); close();
await sleep(300); await sleep(300);
switch (e.message) { switch ((e as Error).message) {
case VAULT_ERRORS.noVault: case VAULT_ERRORS.noVault:
openVault({ openVault({
item: item, item: item,
@@ -775,37 +814,36 @@ export const useActions = ({
id: "favorite", id: "favorite",
title: !item.favorite ? strings.favorite() : strings.unfavorite(), title: !item.favorite ? strings.favorite() : strings.unfavorite(),
icon: item.favorite ? "star-off" : "star-outline", icon: item.favorite ? "star-off" : "star-outline",
func: addToFavorites, onPress: addToFavorites,
close: false, isToggle: true,
check: true, checked: item.favorite,
on: item.favorite,
pro: true, pro: true,
color: "orange" activeColor: "orange"
}, },
{ {
id: "remove-from-notebook", id: "remove-from-notebook",
title: strings.removeFromNotebook(), title: strings.removeFromNotebook(),
hidden: noteInCurrentNotebook, hidden: noteInCurrentNotebook,
icon: "minus-circle-outline", icon: "minus-circle-outline",
func: removeNoteFromNotebook onPress: removeNoteFromNotebook
}, },
{ {
id: "attachments", id: "attachments",
title: strings.attachments(), title: strings.attachments(),
icon: "attachment", icon: "attachment",
func: showAttachments onPress: showAttachments
}, },
{ {
id: "history", id: "history",
title: strings.history(), title: strings.history(),
icon: "history", icon: "history",
func: openHistory onPress: openHistory
}, },
{ {
id: "copy-link", id: "copy-link",
title: strings.copyLink(), title: strings.copyLink(),
icon: "link", icon: "link",
func: () => { onPress: () => {
Clipboard.setString(createInternalLink("note", item.id)); Clipboard.setString(createInternalLink("note", item.id));
ToastManager.show({ ToastManager.show({
heading: strings.linkCopied(), heading: strings.linkCopied(),
@@ -819,7 +857,7 @@ export const useActions = ({
id: "reminders", id: "reminders",
title: strings.dataTypesPluralCamelCase.reminder(), title: strings.dataTypesPluralCamelCase.reminder(),
icon: "clock-outline", icon: "clock-outline",
func: async () => { onPress: async () => {
RelationsList.present({ RelationsList.present({
reference: item, reference: item,
referenceType: "reminder", referenceType: "reminder",
@@ -833,121 +871,108 @@ export const useActions = ({
icon: "plus" icon: "plus"
} }
}); });
}, }
close: false
}, },
{ {
id: "copy", id: "copy",
title: strings.copy(), title: strings.copy(),
icon: "content-copy", icon: "content-copy",
func: copyContent onPress: copyContent
}, },
{ {
id: "share", id: "share",
title: strings.share(), title: strings.share(),
icon: "share-variant", icon: "share-variant",
func: shareNote onPress: shareNote
}, },
{ {
id: "read-only", id: "read-only",
title: strings.readOnly(), title: strings.readOnly(),
icon: "pencil-lock", icon: "pencil-lock",
func: toggleReadyOnlyMode, onPress: toggleReadyOnlyMode,
on: item.readonly checked: item.readonly
}, },
{ {
id: "local-only", id: "local-only",
title: strings.syncOff(), title: strings.syncOff(),
icon: "sync-off", icon: "sync-off",
func: toggleLocalOnly, onPress: toggleLocalOnly,
on: item.localOnly checked: item.localOnly
}, },
{ {
id: "duplicate", id: "duplicate",
title: strings.duplicate(), title: strings.duplicate(),
icon: "content-duplicate", icon: "content-duplicate",
func: duplicateNote onPress: duplicateNote
}, },
{ {
id: "add-reminder", id: "add-reminder",
title: strings.remindMe(), title: strings.remindMe(),
icon: "clock-plus-outline", icon: "clock-plus-outline",
func: () => { onPress: () => {
ReminderSheet.present(undefined, { id: item.id, type: "note" }); ReminderSheet.present(undefined, { id: item.id, type: "note" });
}, }
close: true
}, },
{ {
id: "lock-unlock", id: "lock-unlock",
title: locked ? strings.unlock() : strings.lock(), title: locked ? strings.unlock() : strings.lock(),
icon: locked ? "lock-open-outline" : "key-outline", icon: locked ? "lock-open-outline" : "key-outline",
func: addToVault, onPress: addToVault,
on: locked checked: locked
}, },
{ {
id: "publish", id: "publish",
title: isPublished ? strings.published() : strings.publish(), title: isPublished ? strings.published() : strings.publish(),
icon: "cloud-upload-outline", icon: "cloud-upload-outline",
on: isPublished, checked: isPublished,
func: publishNote onPress: publishNote
}, },
{ {
id: "export", id: "export",
title: strings.export(), title: strings.export(),
icon: "export", icon: "export",
func: exportNote onPress: exportNote
},
{
id: "pin-to-notifications",
title: notifPinned
? strings.unpinFromNotifications()
: strings.pinToNotifications(),
icon: "message-badge-outline",
on: !!notifPinned,
func: pinToNotifications
}, },
{ {
id: "notebooks", id: "notebooks",
title: strings.linkNotebooks(), title: strings.linkNotebooks(),
icon: "book-outline", icon: "book-outline",
func: addTo onPress: addTo
}, },
{ {
id: "add-tag", id: "add-tag",
title: strings.addTags(), title: strings.addTags(),
icon: "pound", icon: "pound",
func: addTo onPress: addTo
}, },
{ {
id: "references", id: "references",
title: strings.references(), title: strings.references(),
icon: "vector-link", icon: "vector-link",
func: () => { onPress: () => {
ReferencesList.present({ ReferencesList.present({
reference: item as ItemReference reference: item as ItemReference
}); });
} }
} }
); );
}
useEffect(() => { if (Platform.OS === "android") {
const { currentRoute, focusedRouteId } = useNavigationStore.getState(); actions.push({
if (item.type !== "note" || currentRoute !== "Notebook" || !focusedRouteId) id: "pin-to-notifications",
return; title: notifPinned
? strings.unpinFromNotifications()
!!db.relations : strings.pinToNotifications(),
.to(item, "notebook") icon: "message-badge-outline",
.selector.find((v) => v("id", "==", focusedRouteId)) checked: !!notifPinned,
.then((notebook) => { onPress: pinToNotifications
setNoteInCurrentNotebook(!!notebook);
}); });
}, [item]); }
}
actions.push({ actions.push({
id: "trash", id: "trash",
@@ -960,7 +985,7 @@ export const useActions = ({
: strings.moveToTrash(), : strings.moveToTrash(),
icon: "delete-outline", icon: "delete-outline",
type: "error", type: "error",
func: deleteItem onPress: deleteItem
}); });
return actions; return actions;

View File

@@ -59,8 +59,7 @@ import {
import { import {
clearAppState, clearAppState,
editorController, editorController,
editorState, editorState
setAppState
} from "../screens/editor/tiptap/utils"; } from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state"; import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup"; import BackupService from "../services/backup";
@@ -103,7 +102,7 @@ import {
refreshNotesPage refreshNotesPage
} from "../utils/events"; } from "../utils/events";
import { getGithubVersion } from "../utils/github-version"; import { getGithubVersion } from "../utils/github-version";
import { tabBarRef } from "../utils/global-refs"; import { fluidTabsRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module"; import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
@@ -168,7 +167,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
clearAppState(); clearAppState();
editorState().movedAway = false; editorState().movedAway = false;
eSendEvent(eOnLoadNote, { newNote: true }); eSendEvent(eOnLoadNote, { newNote: true });
tabBarRef.current?.goToPage(1, false); fluidTabsRef.current?.goToPage(1, false);
return; return;
} else if (url.startsWith("https://notesnook.com/open_note")) { } else if (url.startsWith("https://notesnook.com/open_note")) {
const id = new URL(url).searchParams.get("id"); const id = new URL(url).searchParams.get("id");
@@ -178,7 +177,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, { eSendEvent(eOnLoadNote, {
item: note item: note
}); });
tabBarRef.current?.goToPage(1, false); fluidTabsRef.current?.goToPage(1, false);
} }
} }
} else if (url.startsWith("https://notesnook.com/open_reminder")) { } else if (url.startsWith("https://notesnook.com/open_reminder")) {

View File

@@ -19,13 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { import {
Attachment, Attachment,
Color, Color,
HistorySession,
Note, Note,
Notebook, Notebook,
Reminder, Reminder,
Shortcut, Shortcut,
Tag, Tag,
VirtualizedGrouping, VirtualizedGrouping
HistorySession
} from "@notesnook/core"; } from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { db } from "../common/database"; import { db } from "../common/database";
@@ -34,8 +34,8 @@ import {
eSubscribeEvent, eSubscribeEvent,
eUnSubscribeEvent eUnSubscribeEvent
} from "../services/event-manager"; } from "../services/event-manager";
import { eDBItemUpdate } from "../utils/events";
import { useSettingStore } from "../stores/use-setting-store"; import { useSettingStore } from "../stores/use-setting-store";
import { eDBItemUpdate } from "../utils/events";
type ItemTypeKey = { type ItemTypeKey = {
note: Note; note: Note;
@@ -55,7 +55,8 @@ function isValidIdOrIndex(idOrIndex?: string | number) {
export const useDBItem = <T extends keyof ItemTypeKey>( export const useDBItem = <T extends keyof ItemTypeKey>(
idOrIndex?: string | number, idOrIndex?: string | number,
type?: T, type?: T,
items?: VirtualizedGrouping<ItemTypeKey[T]> items?: VirtualizedGrouping<ItemTypeKey[T]>,
onItemUpdated?: (item?: ItemTypeKey[T]) => void
): [ItemTypeKey[T] | undefined, () => void] => { ): [ItemTypeKey[T] | undefined, () => void] => {
const [item, setItem] = useState<ItemTypeKey[T]>(); const [item, setItem] = useState<ItemTypeKey[T]>();
const itemIdRef = useRef<string>(); const itemIdRef = useRef<string>();
@@ -67,15 +68,15 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
} }
useEffect(() => { useEffect(() => {
const onUpdateItem = (itemId?: string) => { const onUpdateItem = async (itemId?: string) => {
if (typeof itemId === "string" && itemId !== itemIdRef.current) return; if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return; if (!isValidIdOrIndex(idOrIndex)) return;
if (items && typeof idOrIndex === "number") { if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => { const item = (await items.item(idOrIndex))?.item;
setItem(item.item); setItem(item);
itemIdRef.current = item.item?.id; itemIdRef.current = item?.id;
}); onItemUpdated?.(item);
} else { } else {
if (!(db as any)[type + "s"][type]) { if (!(db as any)[type + "s"][type]) {
console.warn( console.warn(
@@ -83,12 +84,12 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
`db.${type}s.${type}(id: string)` `db.${type}s.${type}(id: string)`
); );
} else { } else {
(db as any)[type + "s"] const item = await (db as any)[type + "s"]?.[type]?.(
?.[type]?.(idOrIndex as string) idOrIndex as string
.then((item: ItemTypeKey[T]) => { );
setItem(item); setItem(item);
itemIdRef.current = item.id; itemIdRef.current = item.id;
}); onItemUpdated?.(item);
} }
} }
}; };
@@ -110,7 +111,7 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
return () => { return () => {
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem); eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
}; };
}, [idOrIndex, type, items]); }, [idOrIndex, type, items, onItemUpdated]);
return [ return [
isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined, isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined,

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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Notebook, VirtualizedGrouping } from "@notesnook/core"; import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import React, { useEffect, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { db } from "../common/database"; import { db } from "../common/database";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager"; import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { eGroupOptionsUpdated, eOnNotebookUpdated } from "../utils/events"; import { eGroupOptionsUpdated, eOnNotebookUpdated } from "../utils/events";
@@ -26,17 +26,20 @@ import { useDBItem, useTotalNotes } from "./use-db-item";
export const useNotebook = ( export const useNotebook = (
id?: string | number, id?: string | number,
items?: VirtualizedGrouping<Notebook>, items?: VirtualizedGrouping<Notebook>,
nestedNotebooks?: boolean nestedNotebooks?: boolean,
countNotes?: boolean
) => { ) => {
const [item, refresh] = useDBItem(id, "notebook", items);
const groupOptions = db.settings.getGroupOptions("notebooks"); const groupOptions = db.settings.getGroupOptions("notebooks");
const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>(); const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>();
const { totalNotes: nestedNotebookNotesCount, getTotalNotes } = const { totalNotes: nestedNotebookNotesCount, getTotalNotes } =
useTotalNotes("notebook"); useTotalNotes("notebook");
const getTotalNotesRef = useRef(getTotalNotes);
getTotalNotesRef.current = getTotalNotes;
const onItemUpdated = React.useCallback(
(item?: Notebook) => {
if (!item) return;
const onRequestUpdate = React.useCallback(() => { if (nestedNotebooks) {
if (!item?.id) return;
const selector = db.relations.from( const selector = db.relations.from(
{ {
type: "notebook", type: "notebook",
@@ -44,9 +47,8 @@ export const useNotebook = (
}, },
"notebook" "notebook"
).selector; ).selector;
selector.ids().then((notebookIds) => { selector.ids().then((notebookIds) => {
getTotalNotes(notebookIds); getTotalNotesRef.current(notebookIds);
}); });
selector selector
@@ -54,28 +56,31 @@ export const useNotebook = (
.then((notebooks) => { .then((notebooks) => {
setNotebooks(notebooks); setNotebooks(notebooks);
}); });
}, [getTotalNotes, item?.id]);
useEffect(() => {
if (nestedNotebooks) {
onRequestUpdate();
} }
}, [item?.id, onRequestUpdate, nestedNotebooks]);
if (countNotes) {
getTotalNotesRef.current([item?.id]);
}
},
[countNotes, nestedNotebooks]
);
const [item, refresh] = useDBItem(id, "notebook", items, onItemUpdated);
const itemRef = useRef(item);
itemRef.current = item;
const refreshRef = useRef(refresh);
refreshRef.current = refresh;
useEffect(() => { useEffect(() => {
const onNotebookUpdate = (id?: string) => { const onNotebookUpdate = (id?: string) => {
if (typeof id === "string" && id !== id) return; if (typeof id === "string" && id !== id) return;
setImmediate(() => { refreshRef.current();
if (nestedNotebooks) {
onRequestUpdate();
}
refresh();
});
}; };
const onUpdate = (type: string) => { const onUpdate = (type: string) => {
if (type !== "notebooks") return; if (type !== "notebooks") return;
onRequestUpdate(); refreshRef.current();
}; };
eSubscribeEvent(eGroupOptionsUpdated, onUpdate); eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
@@ -84,13 +89,14 @@ export const useNotebook = (
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate); eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate); eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
}; };
}, [onRequestUpdate, item?.id, refresh, nestedNotebooks]); }, [nestedNotebooks]);
return { return {
notebook: item, notebook: item,
nestedNotebookNotesCount, nestedNotebookNotesCount,
nestedNotebooks: item ? notebooks : undefined, nestedNotebooks: item ? notebooks : undefined,
onUpdate: onRequestUpdate, onUpdate: () => refresh(),
groupOptions groupOptions,
notesCount: !item ? 0 : nestedNotebookNotesCount(item?.id)
}; };
}; };

View File

@@ -44,7 +44,7 @@ export const useShortcutManager = ({
shortcuts = defaultShortcuts shortcuts = defaultShortcuts
}: { }: {
onShortcutPressed: (shortcut: ShortcutItem | null) => void; onShortcutPressed: (shortcut: ShortcutItem | null) => void;
shortcuts: ShortcutItem[]; shortcuts?: ShortcutItem[];
}) => { }) => {
const initialShortcutRecieved = useRef(false); const initialShortcutRecieved = useRef(false);

View File

@@ -0,0 +1,559 @@
/*
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 { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import {
activateKeepAwake,
deactivateKeepAwake
} from "@sayem314/react-native-keep-awake";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState
} from "react";
import { Dimensions, LayoutChangeEvent, Platform, View } from "react-native";
import {
addOrientationListener,
addSpecificOrientationListener,
getInitialOrientation,
getSpecificOrientation,
removeOrientationListener,
removeSpecificOrientationListener
} from "react-native-orientation";
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming
} from "react-native-reanimated";
import { SafeAreaView } from "react-native-safe-area-context";
import { notesnook } from "../../e2e/test.ids";
import { db } from "../common/database";
import { FluidPanels } from "../components/fluid-panels";
import { SideMenu } from "../components/side-menu";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { useShortcutManager } from "../hooks/use-shortcut-manager";
import { hideAllTooltips } from "../hooks/use-tooltip";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState,
getAppState
} from "../screens/editor/tiptap/utils";
import { EditorWrapper } from "../screens/editor/wrapper";
import { DDS } from "../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../services/event-manager";
import { useSettingStore } from "../stores/use-setting-store";
import {
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eUnlockNote
} from "../utils/events";
import { editorRef, fluidTabsRef } from "../utils/global-refs";
import { AppNavigationStack } from "./navigation-stack";
const MOBILE_SIDEBAR_SIZE = 0.85;
const valueLimiter = (value: number, min: number, max: number) => {
return value < min ? min : value > max ? max : value;
};
export const FluidPanelsView = React.memo(
() => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const setFullscreen = useSettingStore((state) => state.setFullscreen);
const fullscreen = useSettingStore((state) => state.fullscreen);
const setDeviceModeState = useSettingStore((state) => state.setDeviceMode);
const dimensions = useSettingStore((state) => state.dimensions);
const setDimensions = useSettingStore((state) => state.setDimensions);
const insets = useGlobalSafeAreaInsets();
const animatedOpacity = useSharedValue(0);
const animatedTranslateY = useSharedValue(-9999);
const overlayRef = useRef<Animated.View>(null);
const [orientation, setOrientation] = useState(getInitialOrientation());
useShortcutManager({
onShortcutPressed: async (item) => {
if (!item && getAppState()) {
editorState().movedAway = false;
fluidTabsRef.current?.goToPage(1, false);
return;
}
if (item?.type === "notesnook.action.newnote") {
clearAppState();
if (!fluidTabsRef.current) {
setTimeout(() => {
eSendEvent(eOnLoadNote, { newNote: true });
editorState().movedAway = false;
fluidTabsRef.current?.goToPage(1, false);
}, 3000);
return;
}
eSendEvent(eOnLoadNote, { newNote: true });
editorState().movedAway = false;
fluidTabsRef.current?.goToPage(1, false);
}
}
});
useEffect(() => {
const onOrientationChange = (o: string, o2: string) => {
setOrientation(o || o2);
};
if (Platform.OS === "ios") {
addSpecificOrientationListener(onOrientationChange);
getSpecificOrientation && getSpecificOrientation(onOrientationChange);
} else {
addOrientationListener(onOrientationChange);
}
return () => {
removeSpecificOrientationListener(onOrientationChange);
removeOrientationListener(onOrientationChange);
};
}, []);
const showFullScreenEditor = useCallback(() => {
setFullscreen(true);
if (deviceMode === "smallTablet") {
fluidTabsRef.current?.openDrawer(false);
}
editorRef.current?.setNativeProps({
style: {
width: dimensions.width,
zIndex: 999,
paddingHorizontal:
deviceMode === "smallTablet"
? dimensions.width * 0
: dimensions.width * 0.15
}
});
}, [deviceMode, dimensions.width, setFullscreen]);
const closeFullScreenEditor = useCallback(
(current: string) => {
const _deviceMode = current || deviceMode;
if (_deviceMode === "smallTablet") {
fluidTabsRef.current?.closeDrawer(false);
}
setFullscreen(false);
editorController.current?.commands.updateSettings({
fullscreen: false
});
editorRef.current?.setNativeProps({
style: {
width:
_deviceMode === "smallTablet"
? dimensions.width -
valueLimiter(dimensions.width * 0.4, 300, 450)
: dimensions.width * 0.48,
zIndex: null,
paddingHorizontal: 0
}
});
if (_deviceMode === "smallTablet") {
fluidTabsRef.current?.goToIndex(1, false);
}
if (_deviceMode === "mobile") {
fluidTabsRef.current?.goToIndex(2, false);
}
},
[deviceMode, dimensions.width, setFullscreen]
);
const toggleView = useCallback(
(show: boolean) => {
animatedTranslateY.value = show ? 0 : -9999;
},
[animatedTranslateY]
);
useEffect(() => {
if (!fluidTabsRef.current?.isDrawerOpen()) {
toggleView(false);
}
eSubscribeEvent(eOpenFullscreenEditor, showFullScreenEditor);
eSubscribeEvent(eCloseFullscreenEditor, closeFullScreenEditor);
return () => {
eUnSubscribeEvent(eOpenFullscreenEditor, showFullScreenEditor);
eUnSubscribeEvent(eCloseFullscreenEditor, closeFullScreenEditor);
};
}, [
deviceMode,
dimensions,
colors,
showFullScreenEditor,
closeFullScreenEditor,
toggleView
]);
const setDeviceMode = React.useCallback(
(current: string | null, size: { width: number; height: number }) => {
setDeviceModeState(current);
const needsLayout = current !== deviceMode;
if (fullscreen && current !== "mobile") {
// Runs after size is set via state.
setTimeout(() => {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
}, 1);
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
style: {
position: "relative",
width:
current === "tablet"
? size.width * 0.48
: current === "smallTablet"
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
zIndex: null,
paddingHorizontal: 0
}
});
}
if (!needsLayout) {
return;
}
const state = getAppState();
switch (current) {
case "tablet":
fluidTabsRef.current?.goToIndex(0, false);
break;
case "smallTablet":
if (!fullscreen) {
fluidTabsRef.current?.closeDrawer(false);
} else {
fluidTabsRef.current?.openDrawer(false);
}
break;
case "mobile":
if (
state &&
editorState().movedAway === false &&
useTabStore.getState().getCurrentNoteId()
) {
fluidTabsRef.current?.goToIndex(2, false);
} else {
fluidTabsRef.current?.goToIndex(1, false);
}
break;
}
},
[deviceMode, fullscreen, setDeviceModeState]
);
const checkDeviceType = React.useCallback(
(size: { width: number; height: number }) => {
setDimensions({
width: size.width,
height: size.height
});
DDS.setSize(size, orientation);
const nextDeviceMode = DDS.isLargeTablet()
? "tablet"
: DDS.isSmallTab
? "smallTablet"
: "mobile";
setDeviceMode(nextDeviceMode, size);
},
[orientation, setDeviceMode, setDimensions]
);
const _onLayout = React.useCallback(
(event: LayoutChangeEvent) => {
const size = event?.nativeEvent?.layout;
if (!size || (size.width === dimensions.width && deviceMode !== null)) {
DDS.setSize(size, orientation);
setDeviceMode(deviceMode, size);
checkDeviceType(size);
return;
}
checkDeviceType(size);
},
[
checkDeviceType,
deviceMode,
dimensions.width,
orientation,
setDeviceMode
]
);
if (!deviceMode) {
const size = Dimensions.get("window");
checkDeviceType(size);
}
const PANE_OFFSET = useMemo(
() => ({
mobile: {
sidebar: dimensions.width * MOBILE_SIDEBAR_SIZE,
list: dimensions.width + dimensions.width * MOBILE_SIDEBAR_SIZE,
editor: dimensions.width * 2 + dimensions.width * MOBILE_SIDEBAR_SIZE
},
smallTablet: {
sidebar: fullscreen
? 0
: valueLimiter(dimensions.width * 0.3, 300, 350),
list: fullscreen
? 0
: dimensions.width + valueLimiter(dimensions.width * 0.3, 300, 350),
editor: fullscreen
? 0
: dimensions.width + valueLimiter(dimensions.width * 0.3, 300, 350)
},
tablet: {
sidebar: 0,
list: 0,
editor: 0
}
}),
[dimensions.width, fullscreen]
);
const PANE_WIDTHS = useMemo(
() => ({
mobile: {
sidebar: dimensions.width * MOBILE_SIDEBAR_SIZE,
list: dimensions.width,
editor: dimensions.width
},
smallTablet: {
sidebar: valueLimiter(dimensions.width * 0.3, 300, 350),
list: valueLimiter(dimensions.width * 0.4, 300, 450),
editor:
dimensions.width - valueLimiter(dimensions.width * 0.4, 300, 450)
},
tablet: {
sidebar: dimensions.width * 0.22,
list: dimensions.width * 0.3,
editor: dimensions.width * 0.48
}
}),
[dimensions.width]
);
const onScroll = React.useCallback(
(scrollOffset: number) => {
if (!deviceMode) return;
hideAllTooltips();
if (
scrollOffset >
PANE_OFFSET[deviceMode as keyof typeof PANE_OFFSET].sidebar - 10
) {
animatedOpacity.value = 0;
toggleView(false);
} else {
const o = scrollOffset / 300;
const opacity = o < 0 ? 1 : 1 - o;
animatedOpacity.value = opacity;
toggleView(opacity < 0.1 ? false : true);
}
},
[PANE_OFFSET, animatedOpacity, deviceMode, toggleView]
);
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: animatedOpacity.value,
transform: [
{
translateY: animatedTranslateY.value
}
]
};
}, []);
return (
<View
onLayout={_onLayout}
testID={notesnook.ids.default.root}
style={{
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
paddingBottom: Platform.OS === "android" ? insets?.bottom : 0,
marginRight:
orientation === "LANDSCAPE-RIGHT" && Platform.OS === "ios"
? insets.right
: 0,
marginLeft:
orientation === "LANDSCAPE-LEFT" && Platform.OS === "ios"
? insets.left
: 0
}}
>
{deviceMode && PANE_WIDTHS[deviceMode as keyof typeof PANE_WIDTHS] ? (
<FluidPanels
ref={fluidTabsRef}
dimensions={dimensions}
widths={PANE_WIDTHS[deviceMode as keyof typeof PANE_WIDTHS]}
enabled={deviceMode !== "tablet" && !fullscreen}
onScroll={onScroll}
onChangeTab={onChangeTab}
onDrawerStateChange={(state) => {
if (!state) {
useSideBarDraggingStore.setState({
dragging: false
});
}
}}
>
<View
key="1"
style={{
height: "100%",
width: fullscreen
? 0
: PANE_WIDTHS[deviceMode as keyof typeof PANE_WIDTHS]?.sidebar
}}
>
<ScopedThemeProvider value="navigationMenu">
<SideMenu />
</ScopedThemeProvider>
</View>
<View
key="2"
style={{
height: "100%",
width: fullscreen
? 0
: PANE_WIDTHS[deviceMode as keyof typeof PANE_WIDTHS]?.list
}}
>
<ScopedThemeProvider value="list">
{deviceMode === "mobile" ? (
<Animated.View
onTouchEnd={() => {
if (useSideBarDraggingStore.getState().dragging) {
useSideBarDraggingStore.setState({
dragging: false
});
return;
}
fluidTabsRef.current?.closeDrawer();
animatedOpacity.value = withTiming(0);
animatedTranslateY.value = withTiming(-9999);
}}
style={[
{
position: "absolute",
width: "100%",
height: "100%",
zIndex: 999,
backgroundColor: colors.primary.backdrop
},
animatedStyle
]}
ref={overlayRef}
/>
) : null}
<SafeAreaView
style={{
flex: 1
}}
>
<AppNavigationStack />
</SafeAreaView>
</ScopedThemeProvider>
</View>
<ScopedThemeProvider value="editor">
<EditorWrapper key="3" widths={PANE_WIDTHS} />
</ScopedThemeProvider>
</FluidPanels>
) : null}
</View>
);
},
() => true
);
FluidPanelsView.displayName = "FluidPanelsView";
const onChangeTab = async (event: { i: number; from: number }) => {
if (event.i === 2) {
editorState().movedAway = false;
editorState().isFocused = true;
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
newNote: true
});
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab)
?.session?.locked
) {
eSendEvent(eUnlockNote);
}
}
} else {
if (event.from === 2) {
deactivateKeepAwake();
editorState().movedAway = true;
editorState().isFocused = false;
eSendEvent(eOnExitEditor);
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true
}
});
}
}
}
}
};

View File

@@ -1,150 +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 { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as React from "react";
import Container from "../components/container";
import Intro from "../components/intro";
import { NotebookSheet } from "../components/sheets/notebook-sheet";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { hideAllTooltips } from "../hooks/use-tooltip";
import Favorites from "../screens/favorites";
import Home from "../screens/home";
import NotebookScreen from "../screens/notebook";
import Notebooks from "../screens/notebooks";
import { ColoredNotes } from "../screens/notes/colored";
import { Monographs } from "../screens/notes/monographs";
import { TaggedNotes } from "../screens/notes/tagged";
import Reminders from "../screens/reminders";
import { Search } from "../screens/search";
import Settings from "../screens/settings";
import Tags from "../screens/tags";
import Trash from "../screens/trash";
import { eSendEvent } from "../services/event-manager";
import SettingsService from "../services/settings";
import useNavigationStore from "../stores/use-navigation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
const NativeStack = createNativeStackNavigator();
const IntroStack = createNativeStackNavigator();
const IntroStackNavigator = () => {
const { colors } = useThemeColors();
const height = useSettingStore((state) => state.dimensions.height);
return (
<IntroStack.Navigator
screenOptions={{
headerShown: false,
lazy: false,
animation: "none",
contentStyle: {
backgroundColor: colors.primary.background,
minHeight: height
}
}}
initialRouteName={"Intro"}
>
<NativeStack.Screen name="Intro" component={Intro} />
</IntroStack.Navigator>
);
};
const _Tabs = () => {
const { colors } = useThemeColors();
const homepage = SettingsService.get().homepage;
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const height = useSettingStore((state) => state.dimensions.height);
const insets = useGlobalSafeAreaInsets();
const screenHeight = height - (50 + insets.top + insets.bottom);
React.useEffect(() => {
setTimeout(async () => {
useNavigationStore.getState().update(homepage);
}, 1000);
}, [homepage]);
return (
<NativeStack.Navigator
tabBar={() => null}
initialRouteName={!introCompleted ? "Welcome" : homepage}
backBehavior="history"
screenOptions={{
headerShown: false,
lazy: false,
animation: "none",
contentStyle: {
backgroundColor: colors.primary.background,
minHeight: !introCompleted ? undefined : screenHeight
}
}}
>
<NativeStack.Screen name="Welcome" component={IntroStackNavigator} />
<NativeStack.Screen name="Notes" component={Home} />
<NativeStack.Screen name="Notebooks" component={Notebooks} />
<NativeStack.Screen name="Favorites" component={Favorites} />
<NativeStack.Screen name="Trash" component={Trash} />
<NativeStack.Screen name="Tags" component={Tags} />
<NativeStack.Screen name="Settings" component={Settings} />
<NativeStack.Screen name="TaggedNotes" component={TaggedNotes} />
<NativeStack.Screen name="ColoredNotes" component={ColoredNotes} />
<NativeStack.Screen name="Reminders" component={Reminders} />
<NativeStack.Screen
name="Monographs"
initialParams={{
item: { type: "monograph" },
canGoBack: false,
title: strings.monographs()
}}
component={Monographs}
/>
<NativeStack.Screen name="Notebook" component={NotebookScreen} />
<NativeStack.Screen name="Search" component={Search} />
</NativeStack.Navigator>
);
};
const Tabs = React.memo(_Tabs, () => true);
const _NavigationStack = () => {
const clearSelection = useSelectionStore((state) => state.clearSelection);
const loading = useSettingStore((state) => state.isAppLoading);
const onStateChange = React.useCallback(() => {
if (useSelectionStore.getState().selectionMode) {
clearSelection(true);
}
hideAllTooltips();
eSendEvent("navigate");
}, [clearSelection]);
return (
<Container>
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
<Tabs />
</NavigationContainer>
{loading ? null : <NotebookSheet />}
</Container>
);
};
export const NavigationStack = React.memo(_NavigationStack, () => true);

View File

@@ -0,0 +1,158 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as React from "react";
import AppLockedScreen from "../components/app-lock-overlay";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { hideAllTooltips } from "../hooks/use-tooltip";
import Favorites from "../screens/favorites";
import Home from "../screens/home";
import NotebookScreen from "../screens/notebook";
import Notebooks from "../screens/notebooks";
import { ColoredNotes } from "../screens/notes/colored";
import { Monographs } from "../screens/notes/monographs";
import { TaggedNotes } from "../screens/notes/tagged";
import Reminders from "../screens/reminders";
import { Search } from "../screens/search";
import Settings from "../screens/settings";
import Tags from "../screens/tags";
import Trash from "../screens/trash";
import SettingsService from "../services/settings";
import useNavigationStore, {
RouteParams
} from "../stores/use-navigation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import {
appNavigatorRef,
fluidTabsRef,
rootNavigatorRef
} from "../utils/global-refs";
import { FluidPanelsView } from "./fluid-panels-view";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
const AppNavigation = React.memo(
() => {
const { colors } = useThemeColors();
const homepage = SettingsService.get().homepage;
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const height = useSettingStore((state) => state.dimensions.height);
const insets = useGlobalSafeAreaInsets();
const screenHeight = height - (50 + insets.top + insets.bottom);
React.useEffect(() => {
setTimeout(() => {
useNavigationStore.getState().update(homepage as keyof RouteParams);
}, 1000);
}, [homepage]);
return (
<AppStack.Navigator
initialRouteName={!introCompleted ? "Welcome" : homepage}
screenOptions={{
headerShown: false,
animation: "none",
contentStyle: {
backgroundColor: colors.primary.background,
minHeight: !introCompleted ? undefined : screenHeight
}
}}
>
<AppStack.Screen name="Notes" component={Home} />
<AppStack.Screen name="Notebooks" component={Notebooks} />
<AppStack.Screen name="Favorites" component={Favorites} />
<AppStack.Screen name="Trash" component={Trash} />
<AppStack.Screen name="Tags" component={Tags} />
<AppStack.Screen name="TaggedNotes" component={TaggedNotes} />
<AppStack.Screen name="ColoredNotes" component={ColoredNotes} />
<AppStack.Screen name="Reminders" component={Reminders} />
<AppStack.Screen
name="Monographs"
initialParams={{
item: { type: "monograph" },
canGoBack: false,
title: strings.monographs()
}}
component={Monographs}
/>
<AppStack.Screen name="Notebook" component={NotebookScreen} />
<AppStack.Screen name="Search" component={Search} />
</AppStack.Navigator>
);
},
() => true
);
AppNavigation.displayName = "AppNavigation";
export const RootNavigation = () => {
return (
<NavigationContainer ref={rootNavigatorRef} independent>
<RootStack.Navigator
screenOptions={{
headerShown: false
}}
screenListeners={{
focus: (props) => {
if (props.target?.startsWith("FluidPanelsView")) {
fluidTabsRef.current?.unlock();
} else {
fluidTabsRef.current?.lock();
}
}
}}
initialRouteName="FluidPanelsView"
>
<RootStack.Screen name="FluidPanelsView" component={FluidPanelsView} />
<RootStack.Screen name="AppLock" component={AppLockedScreen} />
<AppStack.Screen name="Settings" component={Settings} />
</RootStack.Navigator>
</NavigationContainer>
);
};
export const AppNavigationStack = React.memo(
() => {
const clearSelection = useSelectionStore((state) => state.clearSelection);
const onStateChange = React.useCallback(() => {
if (useSelectionStore.getState().selectionMode) {
clearSelection();
}
hideAllTooltips();
}, [clearSelection]);
return (
<NavigationContainer
independent
onStateChange={onStateChange}
ref={appNavigatorRef}
>
<AppNavigation />
</NavigationContainer>
);
},
() => true
);
AppNavigationStack.displayName = "AppNavigationStack";

View File

@@ -1,541 +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 { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import {
activateKeepAwake,
deactivateKeepAwake
} from "@sayem314/react-native-keep-awake";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, Platform, StatusBar, View } from "react-native";
import {
addOrientationListener,
addSpecificOrientationListener,
getInitialOrientation,
getSpecificOrientation,
removeOrientationListener,
removeSpecificOrientationListener
} from "react-native-orientation";
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming
} from "react-native-reanimated";
import { notesnook } from "../../e2e/test.ids";
import { db } from "../common/database";
import { SideMenu } from "../components/side-menu";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { FluidTabs } from "../components/tabs";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { useShortcutManager } from "../hooks/use-shortcut-manager";
import { hideAllTooltips } from "../hooks/use-tooltip";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
clearAppState,
editorController,
editorState,
getAppState
} from "../screens/editor/tiptap/utils";
import { EditorWrapper } from "../screens/editor/wrapper";
import { DDS } from "../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../services/event-manager";
import { useSettingStore } from "../stores/use-setting-store";
import {
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eUnlockNote
} from "../utils/events";
import { editorRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { NavigationStack } from "./navigation-stack";
const _TabsHolder = () => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const setFullscreen = useSettingStore((state) => state.setFullscreen);
const fullscreen = useSettingStore((state) => state.fullscreen);
const setDeviceModeState = useSettingStore((state) => state.setDeviceMode);
const dimensions = useSettingStore((state) => state.dimensions);
const setDimensions = useSettingStore((state) => state.setDimensions);
const insets = useGlobalSafeAreaInsets();
const animatedOpacity = useSharedValue(0);
const animatedTranslateY = useSharedValue(-9999);
const overlayRef = useRef();
const [orientation, setOrientation] = useState(getInitialOrientation());
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
useShortcutManager({
onShortcutPressed: async (item) => {
if (!item && getAppState()) {
editorState().movedAway = false;
tabBarRef.current?.goToPage(1, false);
return;
}
if (item && item.type === "notesnook.action.newnote") {
clearAppState();
if (!tabBarRef.current) {
await sleep(3000);
eSendEvent(eOnLoadNote, { newNote: true });
editorState().movedAway = false;
tabBarRef.current?.goToPage(1, false);
return;
}
eSendEvent(eOnLoadNote, { newNote: true });
editorState().movedAway = false;
tabBarRef.current?.goToPage(1, false);
}
}
});
const onOrientationChange = (o, o2) => {
setOrientation(o || o2);
};
useEffect(() => {
if (Platform.OS === "ios") {
addSpecificOrientationListener(onOrientationChange);
getSpecificOrientation && getSpecificOrientation(onOrientationChange);
} else {
addOrientationListener(onOrientationChange);
}
return () => {
removeSpecificOrientationListener(onOrientationChange);
removeOrientationListener(onOrientationChange);
};
}, []);
const showFullScreenEditor = useCallback(() => {
setFullscreen(true);
if (deviceMode === "smallTablet") {
tabBarRef.current?.openDrawer(false);
}
editorRef.current?.setNativeProps({
style: {
width: dimensions.width,
zIndex: 999,
paddingHorizontal:
deviceMode === "smallTablet"
? dimensions.width * 0
: dimensions.width * 0.15
}
});
}, [deviceMode, dimensions.width, setFullscreen]);
const closeFullScreenEditor = useCallback(
(current) => {
const _deviceMode = current || deviceMode;
if (_deviceMode === "smallTablet") {
tabBarRef.current?.closeDrawer(false);
}
setFullscreen(false);
editorController.current?.commands.updateSettings({
fullscreen: false
});
editorRef.current?.setNativeProps({
style: {
width:
_deviceMode === "smallTablet"
? dimensions.width -
valueLimiter(dimensions.width * 0.4, 300, 450)
: dimensions.width * 0.48,
zIndex: null,
paddingHorizontal: 0
}
});
if (_deviceMode === "smallTablet") {
tabBarRef.current?.goToIndex(1, false);
}
if (_deviceMode === "mobile") {
tabBarRef.current?.goToIndex(2, false);
}
},
[deviceMode, dimensions.width, setFullscreen]
);
useEffect(() => {
if (!tabBarRef.current?.isDrawerOpen()) {
toggleView(false);
}
eSubscribeEvent(eOpenFullscreenEditor, showFullScreenEditor);
eSubscribeEvent(eCloseFullscreenEditor, closeFullScreenEditor);
return () => {
eUnSubscribeEvent(eOpenFullscreenEditor, showFullScreenEditor);
eUnSubscribeEvent(eCloseFullscreenEditor, closeFullScreenEditor);
};
}, [
deviceMode,
dimensions,
colors,
showFullScreenEditor,
closeFullScreenEditor,
toggleView
]);
const _onLayout = (event) => {
let size = event?.nativeEvent?.layout;
if (!size || (size.width === dimensions.width && deviceMode !== null)) {
DDS.setSize(size, orientation);
setDeviceMode(deviceMode, size);
checkDeviceType(size);
return;
}
checkDeviceType(size);
};
if (!deviceMode) {
const size = Dimensions.get("window");
checkDeviceType(size);
}
function checkDeviceType(size) {
setDimensions({
width: size.width,
height: size.height
});
DDS.setSize(size, orientation);
const nextDeviceMode = DDS.isLargeTablet()
? "tablet"
: DDS.isSmallTab
? "smallTablet"
: "mobile";
setDeviceMode(nextDeviceMode, size);
}
function setDeviceMode(current, size) {
setDeviceModeState(current);
let needsUpdate = current !== deviceMode;
if (fullscreen && current !== "mobile") {
// Runs after size is set via state.
setTimeout(() => {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
}, 1);
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
style: {
position: "relative",
width:
current === "tablet"
? size.width * 0.48
: current === "smallTablet"
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
zIndex: null,
paddingHorizontal: 0
}
});
}
if (!needsUpdate) {
return;
}
const state = getAppState();
switch (current) {
case "tablet":
tabBarRef.current?.goToIndex(0, false);
break;
case "smallTablet":
if (!fullscreen) {
tabBarRef.current?.closeDrawer(false);
} else {
tabBarRef.current?.openDrawer(false);
}
break;
case "mobile":
if (
state &&
editorState().movedAway === false &&
useTabStore.getState().getCurrentNoteId()
) {
tabBarRef.current?.goToIndex(2, false);
} else {
tabBarRef.current?.goToIndex(1, false);
}
break;
}
}
const onScroll = (scrollOffset) => {
hideAllTooltips();
if (scrollOffset > offsets[deviceMode].sidebar - 10) {
animatedOpacity.value = 0;
toggleView(false);
} else {
let o = scrollOffset / 300;
let op = 0;
if (o < 0) {
op = 1;
} else {
op = 1 - o;
}
animatedOpacity.value = op;
toggleView(op < 0.1 ? false : true);
}
};
const toggleView = useCallback(
(show) => {
animatedTranslateY.value = show ? 0 : -9999;
},
[animatedTranslateY]
);
const valueLimiter = (value, min, max) => {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
};
const offsets = {
mobile: {
sidebar: dimensions.width * 0.75,
list: dimensions.width + dimensions.width * 0.75,
editor: dimensions.width * 2 + dimensions.width * 0.75
},
smallTablet: {
sidebar: fullscreen ? 0 : valueLimiter(dimensions.width * 0.3, 300, 350),
list: fullscreen
? 0
: dimensions.width + valueLimiter(dimensions.width * 0.3, 300, 350),
editor: fullscreen
? 0
: dimensions.width + valueLimiter(dimensions.width * 0.3, 300, 350)
},
tablet: {
sidebar: 0,
list: 0,
editor: 0
}
};
const widths = {
mobile: {
sidebar: dimensions.width * 0.75,
list: dimensions.width,
editor: dimensions.width
},
smallTablet: {
sidebar: valueLimiter(dimensions.width * 0.3, 300, 350),
list: valueLimiter(dimensions.width * 0.4, 300, 450),
editor: dimensions.width - valueLimiter(dimensions.width * 0.4, 300, 450)
},
tablet: {
sidebar: dimensions.width * 0.22,
list: dimensions.width * 0.3,
editor: dimensions.width * 0.48
}
};
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: animatedOpacity.value,
transform: [
{
translateY: animatedTranslateY.value
}
]
};
}, []);
// useEffect(() => {
// changeSystemBarColors();
// setTimeout(() => {
// changeSystemBarColors();
// }, 1000);
// }, [colors.primary.background, isDark]);
return (
<View
onLayout={_onLayout}
testID={notesnook.ids.default.root}
style={{
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
paddingBottom: Platform.OS === "android" ? insets?.bottom : 0,
marginRight:
orientation === "LANDSCAPE-RIGHT" && Platform.OS === "ios"
? insets.right
: 0,
marginLeft:
orientation === "LANDSCAPE-LEFT" && Platform.OS === "ios"
? insets.left
: 0
}}
>
<StatusBar translucent={true} backgroundColor="transparent" />
{!introCompleted ? (
<NavigationStack />
) : (
<>
{deviceMode && widths[deviceMode] ? (
<FluidTabs
ref={tabBarRef}
dimensions={dimensions}
widths={widths[deviceMode]}
enabled={deviceMode !== "tablet" && !fullscreen}
onScroll={onScroll}
onChangeTab={onChangeTab}
onDrawerStateChange={(state) => {
if (!state) {
useSideBarDraggingStore.setState({
dragging: false
});
}
}}
>
<View
key="1"
style={{
height: "100%",
width: fullscreen ? 0 : widths[deviceMode]?.sidebar
}}
>
<ScopedThemeProvider value="navigationMenu">
<SideMenu />
</ScopedThemeProvider>
</View>
<View
key="2"
style={{
height: "100%",
width: fullscreen ? 0 : widths[deviceMode]?.list
}}
>
<ScopedThemeProvider value="list">
{deviceMode === "mobile" ? (
<Animated.View
onTouchEnd={() => {
if (useSideBarDraggingStore.getState().dragging) {
useSideBarDraggingStore.setState({
dragging: false
});
return;
}
tabBarRef.current?.closeDrawer();
animatedOpacity.value = withTiming(0);
animatedTranslateY.value = withTiming(-9999);
}}
style={[
{
position: "absolute",
width: "100%",
height: "100%",
zIndex: 999,
backgroundColor: colors.primary.backdrop
},
animatedStyle
]}
ref={overlayRef}
/>
) : null}
<NavigationStack />
</ScopedThemeProvider>
</View>
<ScopedThemeProvider value="editor">
<EditorWrapper
key="3"
widths={widths}
dimensions={dimensions}
/>
</ScopedThemeProvider>
</FluidTabs>
) : null}
</>
)}
</View>
);
};
export const TabHolder = React.memo(_TabsHolder, () => true);
const onChangeTab = async (event) => {
if (event.i === 2) {
editorState().movedAway = false;
editorState().isFocused = true;
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
newNote: true
});
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
?.locked
) {
eSendEvent(eUnlockNote);
}
}
} else {
if (event.from === 2) {
deactivateKeepAwake();
editorState().movedAway = true;
editorState().isFocused = false;
eSendEvent(eOnExitEditor);
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
if (!noteId) continue;
const note = await db.notes.note(noteId);
const locked = note && (await db.vaults.itemExists(note));
if (locked) {
useTabStore.getState().updateTab(tab.id, {
session: {
locked: true
}
});
}
}
}
}
};

View File

@@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import { i18n } from "@lingui/core";
import { strings } from "@notesnook/intl";
import React, { import React, {
forwardRef, forwardRef,
useCallback, useCallback,
@@ -48,7 +46,6 @@ import {
eUnlockWithPassword eUnlockWithPassword
} from "../../utils/events"; } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions"; import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import EditorOverlay from "./loading"; import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source"; import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types"; import { EditorProps, useEditorType } from "./tiptap/types";
@@ -61,6 +58,9 @@ import {
openInternalLink, openInternalLink,
randId randId
} from "./tiptap/utils"; } from "./tiptap/utils";
import { fluidTabsRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import { i18n } from "@lingui/core";
const style: ViewStyle = { const style: ViewStyle = {
height: "100%", height: "100%",
@@ -348,7 +348,7 @@ const useLockedNoteHandler = () => {
}), }),
eSubscribeEvent(eUnlockWithPassword, onSubmit) eSubscribeEvent(eUnlockWithPassword, onSubmit)
]; ];
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) { if (tabRef.current?.session?.locked && fluidTabsRef.current?.page() === 2) {
unlock(); unlock();
} }
return () => { return () => {

View File

@@ -73,7 +73,7 @@ import {
eUnlockWithPassword eUnlockWithPassword
} from "../../../utils/events"; } from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions"; import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs"; import { fluidTabsRef } from "../../../utils/global-refs";
import { useDragState } from "../../settings/editor/state"; import { useDragState } from "../../settings/editor/state";
import { EditorMessage, EditorProps, useEditorType } from "./types"; import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store"; import { useTabStore } from "./use-tab-store";
@@ -259,7 +259,7 @@ export const useEditorEvents = (
if (deviceMode === "mobile") { if (deviceMode === "mobile") {
editorState().movedAway = true; editorState().movedAway = true;
tabBarRef.current?.goToPage(0); fluidTabsRef.current?.goToPage(0);
} }
setTimeout(() => { setTimeout(() => {
@@ -269,7 +269,7 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]); }, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => { const onHardwareBackPress = useCallback(() => {
if (tabBarRef.current?.page() === 2) { if (fluidTabsRef.current?.page() === 2) {
onBackPress(); onBackPress();
return true; return true;
} }

View File

@@ -61,7 +61,7 @@ import {
eShowMergeDialog, eShowMergeDialog,
eUpdateNoteInEditor eUpdateNoteInEditor
} from "../../../utils/events"; } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs"; import { fluidTabsRef } from "../../../utils/global-refs";
import { sleep } from "../../../utils/time"; import { sleep } from "../../../utils/time";
import { unlockVault } from "../../../utils/unlock-vault"; import { unlockVault } from "../../../utils/unlock-vault";
import { onNoteCreated } from "../../notes/common"; import { onNoteCreated } from "../../notes/common";
@@ -995,14 +995,14 @@ export const useEditor = (
if (!appState) return; if (!appState) return;
state.current.isRestoringState = true; state.current.isRestoringState = true;
state.current.currentlyEditing = true; state.current.currentlyEditing = true;
if (tabBarRef.current?.page() === 2) { if (fluidTabsRef.current?.page() === 2) {
state.current.movedAway = false; state.current.movedAway = false;
} }
if (!state.current.editorStateRestored) { if (!state.current.editorStateRestored) {
state.current.isRestoringState = true; state.current.isRestoringState = true;
if (!DDS.isTab) { if (!DDS.isTab) {
tabBarRef.current?.goToPage(1, false); fluidTabsRef.current?.goToPage(1, false);
} }
} }
@@ -1081,12 +1081,12 @@ export const useEditor = (
item: note item: note
}); });
} }
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} else { } else {
noteId = useTabStore.getState().getCurrentNoteId() || null; noteId = useTabStore.getState().getCurrentNoteId() || null;
if (!noteId) { if (!noteId) {
loadNote({ newNote: true }); loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) { if (fluidTabsRef.current?.page() === 1) {
state.current.currentlyEditing = false; state.current.currentlyEditing = false;
} }
} else { } else {

View File

@@ -0,0 +1,66 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { Button } from "../../components/ui/button";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
export const FilterBar = () => {
return (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
marginTop: DefaultAppStyles.GAP,
width: "93%",
alignSelf: "center"
}}
>
{[
{
name: "All"
},
{
name: "Favorites"
}
].map((item) => (
<Button
key={item.name}
title={item.name}
type="secondary"
style={{
height: 25
}}
/>
))}
<Button
key="add"
icon="plus"
type="secondary"
iconSize={SIZE.md}
style={{
height: 25
}}
/>
</View>
);
};

View File

@@ -17,6 +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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { strings } from "@notesnook/intl";
import React from "react"; import React from "react";
import { FloatingButton } from "../../components/container/floating-button"; import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout"; import DelayLayout from "../../components/delay-layout";
@@ -29,7 +30,7 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store"; import useNavigationStore from "../../stores/use-navigation-store";
import { useNotes } from "../../stores/use-notes-store"; import { useNotes } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common"; import { openEditor } from "../notes/common";
import { strings } from "@notesnook/intl"; import { FilterBar } from "./filter-bar";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => { export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes(); const [notes, loading] = useNotes();
@@ -66,6 +67,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
id={route.name} id={route.name}
onPressDefaultRightButton={openEditor} onPressDefaultRightButton={openEditor}
/> />
<FilterBar />
<DelayLayout wait={loading}> <DelayLayout wait={loading}>
<List <List
data={notes} data={notes}

View File

@@ -17,15 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { resolveItems } from "@notesnook/common"; import { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core"; import { Note, Notebook, VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core"; import { strings } from "@notesnook/intl";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout"; import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header"; import { Header } from "../../components/header";
import List from "../../components/list"; import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header"; import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import { Properties } from "../../components/properties";
import SelectionHeader from "../../components/selection-header"; import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook"; import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { IconButton } from "../../components/ui/icon-button"; import { IconButton } from "../../components/ui/icon-button";
@@ -40,8 +42,8 @@ import useNavigationStore, {
import { eUpdateNotebookRoute } from "../../utils/events"; import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks"; import { findRootNotebookId } from "../../utils/notebooks";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { openEditor, setOnFirstSave } from "../notes/common"; import { openEditor, setOnFirstSave } from "../notes/common";
import { strings } from "@notesnook/intl";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => { const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>(); const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
@@ -125,6 +127,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
setNotes(notes); setNotes(notes);
await notes.item(0, resolveItems); await notes.item(0, resolveItems);
syncWithNavigation(); syncWithNavigation();
} else {
Navigation.goBack();
} }
setLoading(false); setLoading(false);
} catch (e) { } catch (e) {
@@ -160,13 +164,18 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
renderedInRoute={route.name} renderedInRoute={route.name}
title={params.current.item?.title} title={params.current.item?.title}
canGoBack={params?.current?.canGoBack} canGoBack={params?.current?.canGoBack}
rightButton={{
name: "dots-vertical",
onPress: () => {
Properties.present(params.current.item);
}
}}
hasSearch={true} hasSearch={true}
onSearch={() => { onSearch={() => {
const selector = db.relations.from( const selector = db.relations.from(
params.current.item, params.current.item,
"note" "note"
).selector; ).selector;
Navigation.push("Search", { Navigation.push("Search", {
placeholder: strings.searchInRoute(params.current.item?.title), placeholder: strings.searchInRoute(params.current.item?.title),
type: "note", type: "note",
@@ -175,19 +184,18 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
items: selector items: selector
}); });
}} }}
titleHiddenOnRender
id={params.current.item?.id} id={params.current.item?.id}
onPressDefaultRightButton={openEditor}
/> />
{breadcrumbs && breadcrumbs.length > 0 ? ( {breadcrumbs && breadcrumbs.length > 0 ? (
<View <View
style={{ style={{
width: "100%", width: "100%",
paddingHorizontal: 12, paddingHorizontal: DefaultAppStyles.GAP,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
flexWrap: "wrap" flexWrap: "wrap",
marginTop: DefaultAppStyles.GAP_VERTICAL
}} }}
> >
<IconButton <IconButton
@@ -260,6 +268,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
}} }}
/> />
</DelayLayout> </DelayLayout>
<FloatingButton onPress={openEditor} />
</> </>
); );
}; };

View File

@@ -27,7 +27,7 @@ import { useRelationStore } from "../../stores/use-relation-store";
import { useTagStore } from "../../stores/use-tag-store"; import { useTagStore } from "../../stores/use-tag-store";
import { eOnLoadNote, eOnNotebookUpdated } from "../../utils/events"; import { eOnLoadNote, eOnNotebookUpdated } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions"; import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs"; import { fluidTabsRef } from "../../utils/global-refs";
import { editorState } from "../editor/tiptap/utils"; import { editorState } from "../editor/tiptap/utils";
export const PLACEHOLDER_DATA = { export const PLACEHOLDER_DATA = {
@@ -58,7 +58,7 @@ export function openEditor() {
eSendEvent(eOnLoadNote, { newNote: true }); eSendEvent(eOnLoadNote, { newNote: true });
editorState().currentlyEditing = true; editorState().currentlyEditing = true;
editorState().movedAway = false; editorState().movedAway = false;
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} else { } else {
eSendEvent(eOnLoadNote, { newNote: true }); eSendEvent(eOnLoadNote, { newNote: true });
} }

View File

@@ -17,18 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react"; import React, { useRef } from "react";
import { Platform, View } from "react-native"; import { Platform, View } from "react-native";
import { TextInput } from "react-native-gesture-handler"; import { TextInput } from "react-native-gesture-handler";
import { IconButton } from "../../components/ui/icon-button"; import { IconButton } from "../../components/ui/icon-button";
import Navigation from "../../services/navigation";
import { SIZE } from "../../utils/size";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets"; import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection"; import Navigation from "../../services/navigation";
import { useSelectionStore } from "../../stores/use-selection-store";
import useNavigationStore from "../../stores/use-navigation-store"; import useNavigationStore from "../../stores/use-navigation-store";
import { strings } from "@notesnook/intl"; import { useSelectionStore } from "../../stores/use-selection-store";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
export const SearchBar = ({ export const SearchBar = ({
onChangeText, onChangeText,
loading loading
@@ -50,13 +50,21 @@ export const SearchBar = ({
return selectionMode && isFocused ? null : ( return selectionMode && isFocused ? null : (
<View <View
style={{ style={{
height: Platform.OS === "android" ? 50 + insets.top : 50, width: "100%",
paddingTop: Platform.OS === "ios" ? 0 : insets.top, paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: Platform.OS === "ios" ? 0 : insets.top + 5
}}
>
<View
style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
flexShrink: 1,
width: "100%", width: "100%",
paddingHorizontal: 12 paddingHorizontal: DefaultAppStyles.GAP_SMALL,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderRadius: 10,
borderColor: colors.primary.border,
borderWidth: 1
}} }}
> >
<IconButton <IconButton
@@ -69,22 +77,17 @@ export const SearchBar = ({
}} }}
color={colors.primary.paragraph} color={colors.primary.paragraph}
type="plain" type="plain"
style={{
paddingLeft: 0,
marginLeft: -5,
marginRight: DDS.isLargeTablet() ? 10 : 7
}}
/> />
<TextInput <TextInput
ref={inputRef} ref={inputRef}
testID="search-input" testID="search-input"
style={{ style={{
fontSize: SIZE.md + 1, fontSize: SIZE.sm,
fontFamily: "OpenSans-Regular", fontFamily: "OpenSans-Regular",
flexGrow: 1, flexGrow: 1,
height: "100%", color: colors.primary.paragraph,
color: colors.primary.paragraph height: 40
}} }}
autoFocus autoFocus
onChangeText={_onChangeText} onChangeText={_onChangeText}
@@ -97,5 +100,6 @@ export const SearchBar = ({
placeholderTextColor={colors.primary.placeholder} placeholderTextColor={colors.primary.placeholder}
/> />
</View> </View>
</View>
); );
}; };

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useEffect } from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import { KeyboardAwareFlatList } from "react-native-keyboard-aware-scroll-view"; import { KeyboardAwareFlatList } from "react-native-keyboard-aware-scroll-view";
import Animated, { FadeInDown } from "react-native-reanimated"; import Animated, { FadeInDown } from "react-native-reanimated";
@@ -26,7 +26,6 @@ import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header"; import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus"; import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import useNavigationStore from "../../stores/use-navigation-store"; import useNavigationStore from "../../stores/use-navigation-store";
import { tabBarRef } from "../../utils/global-refs";
import { components } from "./components"; import { components } from "./components";
import { SectionItem } from "./section-item"; import { SectionItem } from "./section-item";
import { RouteParams, SettingSection } from "./types"; import { RouteParams, SettingSection } from "./types";
@@ -42,16 +41,10 @@ const Group = ({
}: NativeStackScreenProps<RouteParams, "SettingsGroup">) => { }: NativeStackScreenProps<RouteParams, "SettingsGroup">) => {
useNavigationFocus(navigation, { useNavigationFocus(navigation, {
onFocus: () => { onFocus: () => {
tabBarRef.current?.lock();
useNavigationStore.getState().setFocusedRouteId("Settings"); useNavigationStore.getState().setFocusedRouteId("Settings");
return false; return false;
} }
}); });
useEffect(() => {
return () => {
tabBarRef.current?.unlock();
};
}, []);
const renderItem = ({ item }: { item: SettingSection; index: number }) => ( const renderItem = ({ item }: { item: SettingSection; index: number }) => (
<SectionItem item={item} /> <SectionItem item={item} />
); );

View File

@@ -56,7 +56,8 @@ const Home = ({
<Header <Header
renderedInRoute="Settings" renderedInRoute="Settings"
title={strings.routes.Settings()} title={strings.routes.Settings()}
canGoBack={false} canGoBack={true}
hasSearch={false}
id="Settings" id="Settings"
/> />
<DelayLayout delay={300} type="settings"> <DelayLayout delay={300} type="settings">

View File

@@ -24,11 +24,18 @@ import { useThemeColors } from "@notesnook/theme";
import Group from "./group"; import Group from "./group";
import Home from "./home"; import Home from "./home";
import { RouteParams } from "./types"; import { RouteParams } from "./types";
import { SafeAreaView } from "react-native-safe-area-context";
const SettingsStack = createNativeStackNavigator<RouteParams>(); const SettingsStack = createNativeStackNavigator<RouteParams>();
export const Settings = () => { export const Settings = () => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
return ( return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: colors.primary.background
}}
>
<SettingsStack.Navigator <SettingsStack.Navigator
initialRouteName="SettingsHome" initialRouteName="SettingsHome"
screenListeners={{ screenListeners={{
@@ -49,6 +56,7 @@ export const Settings = () => {
<SettingsStack.Screen name="SettingsHome" component={Home} /> <SettingsStack.Screen name="SettingsHome" component={Home} />
<SettingsStack.Screen name="SettingsGroup" component={Group} /> <SettingsStack.Screen name="SettingsGroup" component={Group} />
</SettingsStack.Navigator> </SettingsStack.Navigator>
</SafeAreaView>
); );
}; };

View File

@@ -31,7 +31,7 @@ import { useReminderStore } from "../stores/use-reminder-store";
import { useTagStore } from "../stores/use-tag-store"; import { useTagStore } from "../stores/use-tag-store";
import { useTrashStore } from "../stores/use-trash-store"; import { useTrashStore } from "../stores/use-trash-store";
import { eOnRefreshSearch, eUpdateNotebookRoute } from "../utils/events"; import { eOnRefreshSearch, eUpdateNotebookRoute } from "../utils/events";
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs"; import { appNavigatorRef, fluidTabsRef } from "../utils/global-refs";
import { eSendEvent } from "./event-manager"; import { eSendEvent } from "./event-manager";
/** /**
@@ -122,30 +122,36 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
} }
function navigate<T extends RouteName>(screen: T, params?: RouteParams[T]) { function navigate<T extends RouteName>(screen: T, params?: RouteParams[T]) {
rootNavigatorRef.current?.navigate(screen as any, params); console.log(`Navigation.navigate ${screen} route`);
appNavigatorRef.current?.navigate(screen as any, params);
} }
function goBack() { function goBack() {
rootNavigatorRef.current?.goBack(); appNavigatorRef.current?.goBack();
} }
function push<T extends RouteName>(screen: T, params: RouteParams[T]) { function push<T extends RouteName>(screen: T, params: RouteParams[T]) {
rootNavigatorRef.current?.dispatch(StackActions.push(screen as any, params)); console.log(`Navigation.push ${screen} route`);
appNavigatorRef.current?.dispatch(StackActions.push(screen as any, params));
} }
function replace<T extends RouteName>(screen: T, params: RouteParams[T]) { function replace<T extends RouteName>(screen: T, params: RouteParams[T]) {
rootNavigatorRef.current?.dispatch(StackActions.replace(screen, params)); console.log(`Navigation.replace ${screen} route`);
appNavigatorRef.current?.dispatch(
StackActions.replace(screen as string, params)
);
} }
function popToTop() { function popToTop() {
rootNavigatorRef.current?.dispatch(StackActions.popToTop()); console.log(`Navigation.popToTop`);
appNavigatorRef.current?.dispatch(StackActions.popToTop());
} }
function openDrawer() { function openDrawer() {
tabBarRef.current?.openDrawer(); fluidTabsRef.current?.openDrawer();
} }
function closeDrawer() { function closeDrawer() {
tabBarRef.current?.closeDrawer(); fluidTabsRef.current?.closeDrawer();
} }
const Navigation = { const Navigation = {

View File

@@ -44,7 +44,7 @@ import { useReminderStore } from "../stores/use-reminder-store";
import { useSettingStore } from "../stores/use-setting-store"; import { useSettingStore } from "../stores/use-setting-store";
import { useUserStore } from "../stores/use-user-store"; import { useUserStore } from "../stores/use-user-store";
import { eOnLoadNote } from "../utils/events"; import { eOnLoadNote } from "../utils/events";
import { tabBarRef } from "../utils/global-refs"; import { fluidTabsRef } from "../utils/global-refs";
import { convertNoteToText } from "../utils/note-to-text"; import { convertNoteToText } from "../utils/note-to-text";
import { NotesnookModule } from "../utils/notesnook-module"; import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
@@ -440,7 +440,7 @@ async function loadNote(id: string, jump: boolean) {
const note = await db.notes.note(id); const note = await db.notes.note(id);
if (!note) return; if (!note) return;
if (!DDS.isTab && jump) { if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1); fluidTabsRef.current?.goToPage(1);
} }
NotesnookModule.setAppState( NotesnookModule.setAppState(
JSON.stringify({ JSON.stringify({

View File

@@ -0,0 +1,165 @@
/*
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 { Notebook } from "@notesnook/core";
import create from "zustand";
import { db } from "../common/database";
import { createItemSelectionStore } from "./item-selection-store";
export type TreeItem = {
parentId: string;
notebook: Notebook;
depth: number;
};
function removeTreeItem(tree: TreeItem[], id: string) {
const children: TreeItem[] = [];
let newTree = tree.filter((item) => {
if (item.parentId === id) children.push(item);
return item.notebook.id !== id;
});
for (const child of children) {
newTree = removeTreeItem(newTree, child.notebook.id);
}
return newTree;
}
export function createNotebookTreeStores(
multiSelect: boolean,
selectionEnabled: boolean
) {
const useSideMenuNotebookTreeStore = create<{
tree: TreeItem[];
setTree: (tree: TreeItem[]) => void;
removeItem: (id: string) => void;
addNotebooks: (
parentId: string,
notebooks: Notebook[],
depth: number
) => void;
updateItem: (id: string, notebook: Notebook) => void;
fetchAndAdd: (parentId: string, depth: number) => Promise<void>;
removeChildren: (id: string) => void;
}>((set, get) => ({
tree: [],
setTree(tree) {
set({ tree });
},
updateItem: (id, notebook) => {
const newTree = [...get().tree];
const index = newTree.findIndex((item) => item.notebook.id === id);
newTree[index] = {
...newTree[index],
notebook
};
set({
tree: newTree
});
},
addNotebooks: (parentId: string, notebooks: Notebook[], depth: number) => {
const parentIndex = get().tree.findIndex(
(item) => item.notebook.id === parentId
);
let newTree = get().tree.slice();
for (const item of newTree) {
if (item.parentId === parentId) {
newTree = removeTreeItem(newTree, item.notebook.id);
}
}
newTree.splice(
parentIndex + 1,
0,
...notebooks.map((notebook) => {
return {
parentId,
notebook,
depth: depth
};
})
);
set({
tree: newTree
});
},
removeItem(id) {
set({
tree: removeTreeItem(get().tree, id).slice()
});
},
fetchAndAdd: async (parentId: string, depth: number) => {
const selector = db.relations.from(
{
type: "notebook",
id: parentId
},
"notebook"
).selector;
const grouped = await selector.sorted(
db.settings.getGroupOptions("notebooks")
);
const notebooks: Notebook[] = [];
for (let index = 0; index < grouped.placeholders.length; index++) {
const notebook = (await grouped.item(index)).item;
if (notebook) notebooks.push(notebook);
}
get().addNotebooks(parentId, notebooks, depth);
},
removeChildren(id: string) {
let newTree = get().tree.slice();
for (const item of newTree) {
if (item.parentId === id) {
newTree = removeTreeItem(newTree, item.notebook.id);
}
}
set({
tree: newTree
});
}
}));
const useSideMenuNotebookSelectionStore = createItemSelectionStore(
multiSelect,
selectionEnabled
);
const useSideMenuNotebookExpandedStore = create<{
expanded: {
[id: string]: boolean;
};
setExpanded: (id: string) => void;
}>((set, get) => ({
expanded: {},
setExpanded(id: string) {
set({
expanded: {
...get().expanded,
[id]: !get().expanded[id]
}
});
}
}));
return {
useSideMenuNotebookTreeStore,
useSideMenuNotebookSelectionStore,
useSideMenuNotebookExpandedStore
};
}

View File

@@ -33,6 +33,7 @@ export interface SelectionStore extends State {
enabled?: boolean; enabled?: boolean;
getSelectedItemIds: () => string[]; getSelectedItemIds: () => string[];
getDeselectedItemIds: () => string[]; getDeselectedItemIds: () => string[];
selectAll?: () => void;
} }
export function createItemSelectionStore( export function createItemSelectionStore(

View File

@@ -29,6 +29,7 @@ import {
TrashItem TrashItem
} from "@notesnook/core"; } from "@notesnook/core";
import create, { State } from "zustand"; import create, { State } from "zustand";
import { ParamListBase } from "@react-navigation/core";
export type GenericRouteParam = undefined; export type GenericRouteParam = undefined;
@@ -55,7 +56,7 @@ export type AuthParams = {
canGoBack?: boolean; canGoBack?: boolean;
}; };
export type RouteParams = { export interface RouteParams extends ParamListBase {
Notes: GenericRouteParam; Notes: GenericRouteParam;
Notebooks: { Notebooks: {
canGoBack?: boolean; canGoBack?: boolean;
@@ -80,7 +81,7 @@ export type RouteParams = {
AppLock: AppLockRouteParams; AppLock: AppLockRouteParams;
Reminders: GenericRouteParam; Reminders: GenericRouteParam;
SettingsGroup: GenericRouteParam; SettingsGroup: GenericRouteParam;
}; }
export type RouteName = keyof RouteParams; export type RouteName = keyof RouteParams;

View File

@@ -114,7 +114,7 @@ export interface SettingStore extends State {
dimensions: DimensionsType; dimensions: DimensionsType;
setSettings: (settings: Settings) => void; setSettings: (settings: Settings) => void;
setFullscreen: (fullscreen: boolean) => void; setFullscreen: (fullscreen: boolean) => void;
setDeviceMode: (mode: string) => void; setDeviceMode: (mode: string | null) => void;
setDimensions: (dimensions: DimensionsType) => void; setDimensions: (dimensions: DimensionsType) => void;
isAppLoading: boolean; isAppLoading: boolean;
setAppLoading: (isAppLoading: boolean) => void; setAppLoading: (isAppLoading: boolean) => void;

View File

@@ -25,6 +25,7 @@ import { presentDialog } from "../components/dialog/functions";
import { eSendEvent, ToastManager } from "../services/event-manager"; import { eSendEvent, ToastManager } from "../services/event-manager";
import Navigation from "../services/navigation"; import Navigation from "../services/navigation";
import { useMenuStore } from "../stores/use-menu-store"; import { useMenuStore } from "../stores/use-menu-store";
import { useNotebookStore } from "../stores/use-notebook-store";
import { useRelationStore } from "../stores/use-relation-store"; import { useRelationStore } from "../stores/use-relation-store";
import { useTagStore } from "../stores/use-tag-store"; import { useTagStore } from "../stores/use-tag-store";
import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events"; import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events";
@@ -72,8 +73,10 @@ async function deleteNotebook(id: string, deleteNotes: boolean) {
} }
} }
await db.notebooks.moveToTrash(id); await db.notebooks.moveToTrash(id);
if (parentId) {
eSendEvent(eOnNotebookUpdated, parentId); eSendEvent(eOnNotebookUpdated, parentId);
if (!parentId) {
useNotebookStore.getState().refresh();
} }
} }
@@ -113,7 +116,6 @@ export const deleteItems = async (
if (!result.delete) return; if (!result.delete) return;
for (const id of itemIds) { for (const id of itemIds) {
await deleteNotebook(id, result.deleteNotes); await deleteNotebook(id, result.deleteNotes);
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
} }
} else if (type === "tag") { } else if (type === "tag") {
presentDialog({ presentDialog({

View File

@@ -20,10 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createNavigationContainerRef } from "@react-navigation/native"; import { createNavigationContainerRef } from "@react-navigation/native";
import { createRef } from "react"; import { createRef } from "react";
import { TextInput, View } from "react-native"; import { TextInput, View } from "react-native";
import { TabsRef } from "../components/tabs"; import { TabsRef } from "../components/fluid-panels";
import { RouteParams } from "../stores/use-navigation-store"; import { RouteParams } from "../stores/use-navigation-store";
export const inputRef = createRef<TextInput>(); export const inputRef = createRef<TextInput>();
export const rootNavigatorRef = createNavigationContainerRef<RouteParams>(); export const rootNavigatorRef = createNavigationContainerRef<{
export const tabBarRef = createRef<TabsRef>(); FluidPanelsView: undefined;
AppLock: undefined;
Settings: undefined;
}>();
export const appNavigatorRef = createNavigationContainerRef<RouteParams>();
export const fluidTabsRef = createRef<TabsRef>();
export const editorRef = createRef<View>(); export const editorRef = createRef<View>();

View File

@@ -17,51 +17,69 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Item, ItemType } from "@notesnook/core";
import { Monographs } from "../screens/notes/monographs"; import { Monographs } from "../screens/notes/monographs";
export const MenuItemsList = [
export type SideMenuItem = {
dataType?: ItemType | "monograph";
data?: Item;
title: string;
id: string;
icon: string;
onPress?: (item: SideMenuItem) => void;
onLongPress?: (item: SideMenuItem) => void;
};
export const MenuItemsList: SideMenuItem[] = [
{ {
id: "notes", dataType: "note",
name: "Notes", id: "Notes",
icon: "home-variant-outline", title: "Notes",
close: true icon: "note-outline"
},
// {
// dataType: "notebook",
// id: "Notebooks",
// title: "Notebooks",
// icon: "book-outline"
// },
{
dataType: "note",
id: "Favorites",
title: "Favorites",
icon: "star-outline"
},
// {
// dataType: "tag",
// id: "Tags",
// title: "Tags",
// icon: "pound"
// },
{
dataType: "reminder",
id: "Reminders",
title: "Reminders",
icon: "bell"
}, },
{ {
id: "notebooks", dataType: "monograph",
name: "Notebooks", id: "Monographs",
icon: "book-outline", title: "Monographs",
close: true
},
{
id: "favorites",
name: "Favorites",
icon: "star-outline",
close: true
},
{
id: "tags",
name: "Tags",
icon: "pound",
close: true
},
{
id: "reminders",
name: "Reminders",
icon: "bell",
close: true
},
{
id: "monographs",
name: "Monographs",
icon: "text-box-multiple-outline", icon: "text-box-multiple-outline",
close: true, onPress: () => {
func: () => {
Monographs.navigate(); Monographs.navigate();
} }
}, },
{ {
id: "trash", dataType: "note",
name: "Trash", id: "Archive",
icon: "delete-outline", title: "Archive",
close: true icon: "archive"
},
{
dataType: "note",
id: "Trash",
title: "Trash",
icon: "delete-outline"
} }
]; ];

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { db } from "../common/database"; import { db } from "../common/database";
import { eSendEvent } from "../services/event-manager"; import { eSendEvent } from "../services/event-manager";
import { useNotebookStore } from "../stores/use-notebook-store";
import { eOnNotebookUpdated } from "./events"; import { eOnNotebookUpdated } from "./events";
export async function findRootNotebookId(id: string) { export async function findRootNotebookId(id: string) {
@@ -51,9 +52,14 @@ export async function getParentNotebookId(id: string) {
return relation?.[0]?.fromId; return relation?.[0]?.fromId;
} }
export async function updateNotebook(id?: string) { export async function updateNotebook(id?: string, updateParent?: boolean) {
eSendEvent(eOnNotebookUpdated, id); eSendEvent(eOnNotebookUpdated, id);
if (id) { if (updateParent && id) {
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id)); const parent = await getParentNotebookId(id);
if (parent) {
eSendEvent(eOnNotebookUpdated, parent);
} else {
useNotebookStore.getState().refresh();
}
} }
} }

View File

@@ -81,9 +81,12 @@ export const normalize = (size) => {
return correction(size, 1); return correction(size, 1);
} }
}; };
export const SIZE = {
xxs: normalize(11) * scale.fontScale, function getSize() {
xs: normalize(12.5) * scale.fontScale, return {
xxxs: normalize(11) * scale.fontScale,
xxs: normalize(12.5) * scale.fontScale,
xs: normalize(14) * scale.fontScale,
sm: normalize(15) * scale.fontScale, sm: normalize(15) * scale.fontScale,
md: normalize(16.5) * scale.fontScale, md: normalize(16.5) * scale.fontScale,
lg: normalize(22) * scale.fontScale, lg: normalize(22) * scale.fontScale,
@@ -91,21 +94,20 @@ export const SIZE = {
xxl: normalize(28) * scale.fontScale, xxl: normalize(28) * scale.fontScale,
xxxl: normalize(32) * scale.fontScale xxxl: normalize(32) * scale.fontScale
}; };
}
export const SIZE = getSize();
export function updateSize() { export function updateSize() {
SIZE.xxs = normalize(11) * scale.fontScale; const newSize = getSize();
SIZE.xs = normalize(12.5) * scale.fontScale; for (const key in SIZE) {
SIZE.sm = normalize(15) * scale.fontScale; SIZE[key] = newSize[key];
SIZE.md = normalize(16.5) * scale.fontScale; }
SIZE.lg = normalize(22) * scale.fontScale;
SIZE.xl = normalize(24) * scale.fontScale;
SIZE.xxl = normalize(28) * scale.fontScale;
SIZE.xxxl = normalize(32) * scale.fontScale;
ph = normalize(10) * scale.fontScale; ph = normalize(10) * scale.fontScale;
pv = normalize(10) * scale.fontScale; pv = normalize(10) * scale.fontScale;
} }
export const br = 5; // border radius export const br = 8; // border radius
export var ph = normalize(10); // padding horizontal export var ph = normalize(10); // padding horizontal
export var pv = normalize(10); // padding vertical export var pv = normalize(10); // padding vertical
export const opacity = 0.5; // active opacity export const opacity = 0.5; // active opacity

View File

@@ -16,21 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
export const DefaultAppStyles = {
import React from "react"; GAP: 16,
import DialogProvider from "../components/dialog-provider"; GAP_SMALL: 8,
import { Toast } from "../components/toast"; GAP_VERTICAL: 6,
import { TabHolder } from "./tabs-holder"; GAP_VERTICAL_SMALL: 4
import { ScopedThemeProvider } from "@notesnook/theme";
const _ApplicationHolder = () => {
return (
<>
<TabHolder />
<ScopedThemeProvider value="dialog">
<Toast />
</ScopedThemeProvider>
<DialogProvider />
</>
);
}; };
export const ApplicationHolder = React.memo(_ApplicationHolder, () => true);

View File

@@ -24,7 +24,7 @@ import {
} from "@notesnook/intl/dist/locales/$pseudo-LOCALE.json"; } from "@notesnook/intl/dist/locales/$pseudo-LOCALE.json";
i18n.load({ i18n.load({
en: __DEV__ ? $pseudo : $en en: !__DEV__ ? $pseudo : $en
}); });
setI18nGlobal(i18n); setI18nGlobal(i18n);
i18n.activate("en"); i18n.activate("en");

View File

@@ -73,7 +73,9 @@
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task", "react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot", "react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0", "@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@ammarahmed/react-native-share-extension": "^2.9.0" "@ammarahmed/react-native-share-extension": "^2.9.0",
"react-native-pager-view": "^6.5.1",
"react-native-tab-view": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"detox": "^20.27.6", "detox": "^20.27.6",
@@ -88,6 +90,7 @@
"@tsconfig/react-native": "^3.0.2", "@tsconfig/react-native": "^3.0.2",
"@types/html-to-text": "^8.0.1", "@types/html-to-text": "^8.0.1",
"@types/metro-config": "^0.76.3", "@types/metro-config": "^0.76.3",
"@types/react": "^18.2.39",
"@types/react-native": "^0.69.1", "@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10", "@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",

View File

@@ -1,12 +1,12 @@
{ {
"name": "@notesnook/mobile", "name": "@notesnook/mobile",
"version": "3.0.31", "version": "3.0.32",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@notesnook/mobile", "name": "@notesnook/mobile",
"version": "3.0.31", "version": "3.0.32",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"workspaces": [ "workspaces": [
@@ -14,7 +14,6 @@
"app/" "app/"
], ],
"dependencies": { "dependencies": {
"@ammarahmed/react-native-share-extension": "^2.9.0",
"@notesnook/common": "file:../../packages/common", "@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core", "@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto", "@notesnook/crypto": "file:../../packages/crypto",
@@ -3728,6 +3727,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook/common": "file:../common", "@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl", "@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme", "@notesnook/theme": "file:../theme",
@@ -7743,7 +7743,7 @@
}, },
"../../packages/editor-mobile/node_modules/@types/prop-types": { "../../packages/editor-mobile/node_modules/@types/prop-types": {
"version": "15.7.11", "version": "15.7.11",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"../../packages/editor-mobile/node_modules/@types/q": { "../../packages/editor-mobile/node_modules/@types/q": {
@@ -7763,7 +7763,7 @@
}, },
"../../packages/editor-mobile/node_modules/@types/react": { "../../packages/editor-mobile/node_modules/@types/react": {
"version": "18.2.39", "version": "18.2.39",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@@ -7794,7 +7794,7 @@
}, },
"../../packages/editor-mobile/node_modules/@types/scheduler": { "../../packages/editor-mobile/node_modules/@types/scheduler": {
"version": "0.16.8", "version": "0.16.8",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"../../packages/editor-mobile/node_modules/@types/semver": { "../../packages/editor-mobile/node_modules/@types/semver": {
@@ -12619,7 +12619,7 @@
}, },
"../../packages/editor-mobile/node_modules/immer": { "../../packages/editor-mobile/node_modules/immer": {
"version": "9.0.21", "version": "9.0.21",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -23091,6 +23091,7 @@
}, },
"../../packages/editor/node_modules/js-tokens": { "../../packages/editor/node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"../../packages/editor/node_modules/jsesc": { "../../packages/editor/node_modules/jsesc": {
@@ -23141,6 +23142,7 @@
}, },
"../../packages/editor/node_modules/loose-envify": { "../../packages/editor/node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0" "js-tokens": "^3.0.0 || ^4.0.0"
@@ -23652,6 +23654,7 @@
}, },
"../../packages/editor/node_modules/react": { "../../packages/editor/node_modules/react": {
"version": "18.3.1", "version": "18.3.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
@@ -23670,6 +23673,7 @@
}, },
"../../packages/editor/node_modules/react-dom": { "../../packages/editor/node_modules/react-dom": {
"version": "18.3.1", "version": "18.3.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
@@ -23808,6 +23812,7 @@
}, },
"../../packages/editor/node_modules/scheduler": { "../../packages/editor/node_modules/scheduler": {
"version": "0.23.2", "version": "0.23.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
@@ -28315,6 +28320,7 @@
"@ammarahmed/react-native-background-fetch": "^4.2.2", "@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0", "@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0", "@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@ammarahmed/react-native-share-extension": "^2.9.0",
"@ammarahmed/react-native-sodium": "^1.6.1", "@ammarahmed/react-native-sodium": "^1.6.1",
"@bam.tech/react-native-image-resizer": "3.0.5", "@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^4.1.1", "@callstack/repack": "^4.1.1",
@@ -28357,6 +28363,7 @@
"react-native-navigation-bar-color": "2.0.2", "react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5", "react-native-notification-sounds": "0.5.5",
"react-native-orientation": "github:yamill/react-native-orientation", "react-native-orientation": "github:yamill/react-native-orientation",
"react-native-pager-view": "^6.5.1",
"react-native-pdf": "6.6.2", "react-native-pdf": "6.6.2",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot", "react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-quick-sqlite": "^8.0.6", "react-native-quick-sqlite": "^8.0.6",
@@ -28369,6 +28376,7 @@
"react-native-share": "^12.0.3", "react-native-share": "^12.0.3",
"react-native-svg": "^12.3.0", "react-native-svg": "^12.3.0",
"react-native-swiper-flatlist": "3.2.2", "react-native-swiper-flatlist": "3.2.2",
"react-native-tab-view": "^4.0.2",
"react-native-theme-switch-animation": "^0.6.0", "react-native-theme-switch-animation": "^0.6.0",
"react-native-tooltips": "^1.0.3", "react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0", "react-native-url-polyfill": "^2.0.0",
@@ -28390,6 +28398,7 @@
"@types/html-to-text": "^8.0.1", "@types/html-to-text": "^8.0.1",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/metro-config": "^0.76.3", "@types/metro-config": "^0.76.3",
"@types/react": "^18.2.39",
"@types/react-native": "^0.69.1", "@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10", "@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",
@@ -28590,6 +28599,7 @@
}, },
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@@ -28709,6 +28719,7 @@
}, },
"node_modules/@babel/helper-hoist-variables": { "node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@@ -28968,6 +28979,7 @@
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.25.9" "@babel/helper-plugin-utils": "^7.25.9"
}, },
@@ -28982,6 +28994,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz",
"integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
@@ -29130,6 +29143,7 @@
"version": "7.21.0-placeholder-for-preset-env.2", "version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
}, },
@@ -29142,6 +29156,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
"integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-create-regexp-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6" "@babel/helper-plugin-utils": "^7.18.6"
@@ -29176,6 +29191,7 @@
}, },
"node_modules/@babel/plugin-syntax-class-properties": { "node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13", "version": "7.12.13",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.12.13"
@@ -29188,6 +29204,7 @@
"version": "7.14.5", "version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.14.5" "@babel/helper-plugin-utils": "^7.14.5"
}, },
@@ -29225,6 +29242,7 @@
"version": "7.8.3", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
"integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.8.3" "@babel/helper-plugin-utils": "^7.8.3"
}, },
@@ -29249,6 +29267,7 @@
"version": "7.25.6", "version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz",
"integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -29263,6 +29282,7 @@
"version": "7.25.6", "version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz",
"integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -29277,6 +29297,7 @@
"version": "7.10.4", "version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
"integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.10.4" "@babel/helper-plugin-utils": "^7.10.4"
}, },
@@ -29288,6 +29309,7 @@
"version": "7.8.3", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.8.0" "@babel/helper-plugin-utils": "^7.8.0"
}, },
@@ -29387,6 +29409,7 @@
"version": "7.14.5", "version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
"integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.14.5" "@babel/helper-plugin-utils": "^7.14.5"
}, },
@@ -29414,6 +29437,7 @@
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
"integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-create-regexp-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6" "@babel/helper-plugin-utils": "^7.18.6"
@@ -29442,6 +29466,7 @@
"version": "7.25.4", "version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz",
"integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
@@ -29472,6 +29497,7 @@
}, },
"node_modules/@babel/plugin-transform-block-scoped-functions": { "node_modules/@babel/plugin-transform-block-scoped-functions": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29500,6 +29526,7 @@
"version": "7.25.4", "version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz",
"integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29515,6 +29542,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz",
"integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
@@ -29579,6 +29607,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz",
"integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29594,6 +29623,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz",
"integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -29608,6 +29638,7 @@
"version": "7.25.9", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.25.9" "@babel/helper-plugin-utils": "^7.25.9"
}, },
@@ -29620,6 +29651,7 @@
}, },
"node_modules/@babel/plugin-transform-exponentiation-operator": { "node_modules/@babel/plugin-transform-exponentiation-operator": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
@@ -29636,6 +29668,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz",
"integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3" "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
@@ -29663,6 +29696,7 @@
}, },
"node_modules/@babel/plugin-transform-for-of": { "node_modules/@babel/plugin-transform-for-of": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29693,6 +29727,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz",
"integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-json-strings": "^7.8.3" "@babel/plugin-syntax-json-strings": "^7.8.3"
@@ -29721,6 +29756,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz",
"integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
@@ -29734,6 +29770,7 @@
}, },
"node_modules/@babel/plugin-transform-member-expression-literals": { "node_modules/@babel/plugin-transform-member-expression-literals": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29749,6 +29786,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz",
"integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-module-transforms": "^7.22.5", "@babel/helper-module-transforms": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29779,6 +29817,7 @@
"version": "7.25.0", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-module-transforms": "^7.22.5", "@babel/helper-module-transforms": "^7.22.5",
@@ -29796,6 +29835,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz",
"integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-module-transforms": "^7.22.5", "@babel/helper-module-transforms": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -29825,6 +29865,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz",
"integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -29854,6 +29895,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz",
"integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-numeric-separator": "^7.10.4" "@babel/plugin-syntax-numeric-separator": "^7.10.4"
@@ -29869,6 +29911,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz",
"integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.5", "@babel/compat-data": "^7.22.5",
"@babel/helper-compilation-targets": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.5",
@@ -29885,6 +29928,7 @@
}, },
"node_modules/@babel/plugin-transform-object-super": { "node_modules/@babel/plugin-transform-object-super": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
@@ -29901,6 +29945,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz",
"integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3" "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
@@ -29974,6 +30019,7 @@
}, },
"node_modules/@babel/plugin-transform-property-literals": { "node_modules/@babel/plugin-transform-property-literals": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -30043,6 +30089,7 @@
}, },
"node_modules/@babel/plugin-transform-regenerator": { "node_modules/@babel/plugin-transform-regenerator": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5",
@@ -30059,6 +30106,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz",
"integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -30151,6 +30199,7 @@
"version": "7.24.8", "version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz",
"integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -30181,6 +30230,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz",
"integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
}, },
@@ -30195,6 +30245,7 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz",
"integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -30224,6 +30275,7 @@
"version": "7.25.4", "version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz",
"integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5" "@babel/helper-plugin-utils": "^7.22.5"
@@ -30239,6 +30291,7 @@
"version": "7.25.4", "version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz",
"integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.5", "@babel/compat-data": "^7.22.5",
"@babel/helper-compilation-targets": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.5",
@@ -30332,6 +30385,7 @@
"version": "0.1.6", "version": "0.1.6",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz",
"integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-proposal-unicode-property-regex": "^7.4.4", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
@@ -30347,6 +30401,7 @@
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -33650,7 +33705,8 @@
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
}, },
"node_modules/@types/graceful-fs": { "node_modules/@types/graceful-fs": {
"version": "4.1.6", "version": "4.1.6",
@@ -33756,14 +33812,14 @@
}, },
"node_modules/@types/prop-types": { "node_modules/@types/prop-types": {
"version": "15.7.5", "version": "15.7.5",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.18", "version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@@ -34119,6 +34175,7 @@
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6" "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@@ -34127,22 +34184,26 @@
"node_modules/@webassemblyjs/floating-point-hex-parser": { "node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"dev": true
}, },
"node_modules/@webassemblyjs/helper-api-error": { "node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"dev": true
}, },
"node_modules/@webassemblyjs/helper-buffer": { "node_modules/@webassemblyjs/helper-buffer": {
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
"dev": true
}, },
"node_modules/@webassemblyjs/helper-numbers": { "node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6",
@@ -34152,12 +34213,14 @@
"node_modules/@webassemblyjs/helper-wasm-bytecode": { "node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true
}, },
"node_modules/@webassemblyjs/helper-wasm-section": { "node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@@ -34169,6 +34232,7 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
"dependencies": { "dependencies": {
"@xtuc/ieee754": "^1.2.0" "@xtuc/ieee754": "^1.2.0"
} }
@@ -34177,6 +34241,7 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
"dependencies": { "dependencies": {
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
} }
@@ -34184,12 +34249,14 @@
"node_modules/@webassemblyjs/utf8": { "node_modules/@webassemblyjs/utf8": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"dev": true
}, },
"node_modules/@webassemblyjs/wasm-edit": { "node_modules/@webassemblyjs/wasm-edit": {
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@@ -34205,6 +34272,7 @@
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@@ -34217,6 +34285,7 @@
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@@ -34228,6 +34297,7 @@
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6",
@@ -34241,6 +34311,7 @@
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
@@ -34298,12 +34369,14 @@
"node_modules/@xtuc/ieee754": { "node_modules/@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true
}, },
"node_modules/@xtuc/long": { "node_modules/@xtuc/long": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
}, },
"node_modules/@yarnpkg/lockfile": { "node_modules/@yarnpkg/lockfile": {
"version": "1.1.0", "version": "1.1.0",
@@ -35302,6 +35375,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
} }
@@ -35732,7 +35806,7 @@
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.2", "version": "3.1.2",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/date-fns": { "node_modules/date-fns": {
@@ -36332,6 +36406,7 @@
"version": "5.17.1", "version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"tapable": "^2.2.0" "tapable": "^2.2.0"
@@ -36451,7 +36526,8 @@
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
"dev": true
}, },
"node_modules/es-object-atoms": { "node_modules/es-object-atoms": {
"version": "1.0.0", "version": "1.0.0",
@@ -36796,6 +36872,7 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^4.1.1" "estraverse": "^4.1.1"
@@ -36808,6 +36885,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
} }
@@ -36971,6 +37049,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"dependencies": { "dependencies": {
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
}, },
@@ -36982,6 +37061,7 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
} }
@@ -36990,6 +37070,7 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -37749,7 +37830,8 @@
"node_modules/glob-to-regexp": { "node_modules/glob-to-regexp": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
}, },
"node_modules/global": { "node_modules/global": {
"version": "4.4.0", "version": "4.4.0",
@@ -39838,7 +39920,8 @@
"node_modules/json-parse-even-better-errors": { "node_modules/json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
}, },
"node_modules/json-schema-ref-resolver": { "node_modules/json-schema-ref-resolver": {
"version": "1.0.1", "version": "1.0.1",
@@ -40314,6 +40397,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"engines": { "engines": {
"node": ">=6.11.5" "node": ">=6.11.5"
} }
@@ -42543,6 +42627,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": { "dependencies": {
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
} }
@@ -42564,30 +42649,6 @@
"node": ">=0.10.0" "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==",
"license": "MIT",
"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==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/react-freeze": { "node_modules/react-freeze": {
"version": "1.0.3", "version": "1.0.3",
"license": "MIT", "license": "MIT",
@@ -43023,6 +43084,15 @@
"react-native": ">=0.40" "react-native": ">=0.40"
} }
}, },
"node_modules/react-native-pager-view": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.5.1.tgz",
"integrity": "sha512-YdX7bP+rPYvATMU7HzlMq9JaG3ui/+cVRbFZFGW+QshDULANFg9ECR1BA7H7JTIcO/ZgWCwF+1aVmYG5yBA9Og==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-pdf": { "node_modules/react-native-pdf": {
"version": "6.6.2", "version": "6.6.2",
"license": "MIT", "license": "MIT",
@@ -43187,6 +43257,27 @@
"react-native": ">=0.59.0" "react-native": ">=0.59.0"
} }
}, },
"node_modules/react-native-tab-view": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-4.0.2.tgz",
"integrity": "sha512-u70Et9onvAGhcBRda8NO+JgM/10G3kg/jWsbDY7QQSOAri7F7+VVS/4uupxjbCDSkL1BJdUOqhUToK/M2AH34Q==",
"dependencies": {
"use-latest-callback": "^0.2.1"
},
"peerDependencies": {
"react": ">= 18.2.0",
"react-native": "*",
"react-native-pager-view": ">= 6.0.0"
}
},
"node_modules/react-native-tab-view/node_modules/use-latest-callback": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz",
"integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-native-theme-switch-animation": { "node_modules/react-native-theme-switch-animation": {
"version": "0.6.0", "version": "0.6.0",
"license": "MIT", "license": "MIT",
@@ -43507,6 +43598,7 @@
}, },
"node_modules/regenerator-transform": { "node_modules/regenerator-transform": {
"version": "0.15.1", "version": "0.15.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.8.4" "@babel/runtime": "^7.8.4"
@@ -43859,6 +43951,7 @@
}, },
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "6.0.1", "version": "6.0.1",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"randombytes": "^2.1.0" "randombytes": "^2.1.0"
@@ -44516,6 +44609,7 @@
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -44593,6 +44687,7 @@
"version": "5.3.10", "version": "5.3.10",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "^0.3.17", "@jridgewell/trace-mapping": "^0.3.17",
"jest-worker": "^27.4.5", "jest-worker": "^27.4.5",
@@ -44626,6 +44721,7 @@
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"merge-stream": "^2.0.0", "merge-stream": "^2.0.0",
@@ -44639,6 +44735,7 @@
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
}, },
@@ -45215,6 +45312,7 @@
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"dependencies": { "dependencies": {
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2" "graceful-fs": "^4.1.2"
@@ -45238,6 +45336,7 @@
"version": "5.94.0", "version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
@@ -45353,6 +45452,7 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.13.0"
} }

View File

@@ -83,7 +83,16 @@ const EXTRA_ICON_NAMES = [
"arrow-up-bold", "arrow-up-bold",
"login", "login",
"gift", "gift",
"share" "share",
"note-outline",
"weather-sunny",
"weather-night",
"notebook-outline",
"archive",
"home-outline",
"checkbox-blank-outline",
"checkbox-outline",
"checkbox-intermediate"
]; ];
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);

View File

@@ -63,6 +63,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook/common": "file:../common", "@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl", "@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme", "@notesnook/theme": "file:../theme",

View File

@@ -2443,5 +2443,13 @@ Use this if changes from other devices are not appearing on this device. This wi
fontLigatures: () => t`Font ligatures`, fontLigatures: () => t`Font ligatures`,
fontLigaturesDesc: () => fontLigaturesDesc: () =>
t`Enable ligatures for common symbols like →, ←, etc`, t`Enable ligatures for common symbols like →, ←, etc`,
expandSidebar: () => t`Expand sidebar` expandSidebar: () => t`Expand sidebar`,
viewAllLimits: () => `View all limits`,
freePlan: () => t`Free plan`,
proPlan: () => t`Pro plan`,
essentialPlan: () => t`Essential plan`,
believerPlan: () => t`Believer plan`,
storage: () => t`Storage`,
used: () => t`used`,
editProfile: () => t`Edit profile`
}; };

View File

@@ -417,9 +417,9 @@
} }
}, },
"node_modules/@theme-ui/css": { "node_modules/@theme-ui/css": {
"version": "0.16.1", "version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/css/-/css-0.16.1.tgz", "resolved": "https://registry.npmjs.org/@theme-ui/css/-/css-0.16.2.tgz",
"integrity": "sha512-8TO2DbiqPrRyTlGRIElDak/p0M4ykyd8LkeavyOF/sTE9s93AwyFcle6KYYMEULrJP49SyYiEvTif7J7Z50DhA==", "integrity": "sha512-fNe+FhwKC5+7jQfxCRnm3oqYNhMFuiWiLA9OoLBEkt3b4egot29UK1+fqemwiNVjl206e2fPT5Z7uXRdb6LC2A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.10" "csstype": "^3.0.10"