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 { I18nProvider } from "@lingui/react";
import {
ScopedThemeProvider,
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { I18nManager, View } from "react-native";
import { I18nManager, StatusBar } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
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 GlobalSafeAreaProvider from "./components/globalsafearea";
import { Toast } from "./components/toast";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { NotePreviewConfigure } from "./screens/note-preview-configure";
import { RootNavigation } from "./navigation/navigation-stack";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
@@ -64,42 +66,26 @@ const App = (props: { configureMode: "note-preview" }) => {
}, []);
return (
<View
style={{
height: "100%",
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<View
style={{
position: "absolute",
width: "100%",
height: "100%",
zIndex: -1
}}
pointerEvents="none"
>
<SafeAreaProvider>
<GlobalSafeAreaProvider />
</SafeAreaProvider>
</View>
<StatusBar translucent={true} backgroundColor="transparent" />
<GestureHandlerRootView
style={{
height: "100%",
width: "100%"
}}
>
<GlobalSafeAreaProvider />
{props.configureMode === "note-preview" ? (
<NotePreviewConfigure />
) : (
<ApplicationHolder />
<RootNavigation />
)}
<ScopedThemeProvider value="dialog">
<Toast />
</ScopedThemeProvider>
<DialogProvider />
</GestureHandlerRootView>
<AppLockedOverlay />
</View>
</SafeAreaProvider>
);
};

View File

@@ -82,7 +82,7 @@ const verifyUserPassword = async (password: string) => {
}
};
const AppLockedOverlay = () => {
const AppLockedScreen = () => {
const initialLaunchBiometricRequest = useRef(true);
const { colors } = useThemeColors();
const user = getUser();
@@ -203,14 +203,12 @@ const AppLockedOverlay = () => {
}
}, [appState, onUnlockAppRequested, appLocked]);
return appLocked ? (
return (
<KeyboardAwareScrollView
style={{
backgroundColor: colors.primary.background,
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999
height: "100%"
}}
contentContainerStyle={{
justifyContent: "center",
@@ -333,7 +331,7 @@ const AppLockedOverlay = () => {
</View>
</View>
</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 { useRoute } from "@react-navigation/native";
import React, { useCallback, useEffect } from "react";
import { Keyboard, View } from "react-native";
import { Keyboard, TouchableOpacity, View } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
@@ -34,7 +34,7 @@ import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import { Pressable } from "../ui/pressable";
import { DefaultAppStyles } from "../../utils/styles";
interface FloatingButtonProps {
onPress: () => void;
@@ -108,20 +108,24 @@ const FloatingButton = ({
style={[
{
position: "absolute",
right: 12,
right: DefaultAppStyles.GAP,
bottom: 20,
zIndex: 10
},
animatedStyle
]}
>
<Pressable
<TouchableOpacity
testID={notesnook.buttons.add}
type="accent"
accentColor={color}
activeOpacity={0.95}
style={{
...getElevationStyle(5),
borderRadius: 100
...getElevationStyle(10),
borderRadius: 20,
borderTopWidth: 0,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 0,
backgroundColor: colors.primary.background
}}
onPress={onPress}
>
@@ -130,7 +134,9 @@ const FloatingButton = ({
alignItems: "center",
justifyContent: "center",
height: normalize(60),
width: normalize(60)
width: normalize(60),
backgroundColor: colors.primary.shade,
borderRadius: 20
}}
>
<Icon
@@ -141,11 +147,11 @@ const FloatingButton = ({
? "delete"
: "plus"
}
color={colors.primary.accentForeground}
size={SIZE.xxl}
color={colors.primary.accent}
size={SIZE.xxxl}
/>
</View>
</Pressable>
</TouchableOpacity>
</Animated.View>
);
};

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ import {
eUpdateNoteInEditor
} from "../../../utils/events";
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 { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
@@ -553,7 +553,7 @@ export class VaultDialog extends Component {
item: note
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);
fluidTabsRef.current?.goToPage(1);
}
});
}

View File

@@ -37,12 +37,12 @@ import Animated, {
WithSpringConfig,
withTiming
} from "react-native-reanimated";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { getAppState } from "../../screens/editor/tiptap/utils";
import { eSendEvent } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { eClearEditor } from "../../utils/events";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
interface TabProps extends ViewProps {
dimensions: { width: number; height: number };
@@ -66,7 +66,7 @@ export interface TabsRef {
node: RefObject<Animated.View>;
}
export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
{
children,
dimensions,
@@ -81,9 +81,6 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const appState = useMemo(() => getAppState(), []);
const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const translateX = useSharedValue(
widths
? appState &&
@@ -118,7 +115,6 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const isIPhone = Platform.OS === "ios";
useEffect(() => {
if (introCompleted) {
if (deviceMode === "tablet" || fullscreen) {
translateX.value = 0;
} else {
@@ -134,10 +130,19 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
}
isLoaded.current = true;
prevWidths.current = widths;
}
}, [
deviceMode,
widths,
fullscreen,
translateX,
editorPosition,
appState,
onChangeTab
]);
useEffect(() => {
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
if (isDrawerOpen.value) {
if (isDrawerOpen.value && !forcedLock.value) {
translateX.value = withTiming(homePosition);
onDrawerStateChange(false);
isDrawerOpen.value = false;
@@ -149,16 +154,11 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
sub && sub.remove();
};
}, [
introCompleted,
deviceMode,
widths,
fullscreen,
translateX,
isDrawerOpen,
forcedLock.value,
homePosition,
isDrawerOpen,
onDrawerStateChange,
editorPosition,
appState
translateX
]);
useImperativeHandle(
@@ -294,8 +294,8 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
const gesture = Gesture.Pan()
.maxPointers(1)
.enabled(enabled && !disabled)
.activeOffsetX([-5, 5])
.failOffsetY([-15, 15])
.activeOffsetX([-20, 20])
.failOffsetY([-10, 10])
.onBegin((event) => {
locked.value = false;
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/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform, StyleSheet, View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { View } from "react-native";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore, {
RouteName
} from "../../stores/use-navigation-store";
import { RouteName } from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
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 { RightMenus } from "./right-menus";
import { Title } from "./title";
type HeaderRightButton = {
title: string;
onPress: () => void;
};
export const Header = ({
renderedInRoute,
onLeftMenuButtonPress,
title,
titleHiddenOnRender,
headerRightButtons,
id,
accentColor,
isBeta,
canGoBack,
onPressDefaultRightButton,
hasSearch,
onSearch
onSearch,
rightButton
}: {
onLeftMenuButtonPress?: () => void;
renderedInRoute?: RouteName;
id?: string;
title: string;
headerRightButtons?: HeaderRightButton[];
titleHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
canGoBack?: boolean;
onPressDefaultRightButton?: () => void;
hasSearch?: boolean;
onSearch?: () => void;
rightButton?: IconButtonProps;
}) => {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
const [selectedItemsList, selectionMode] = useSelectionStore((state) => [
state.selectedItemsList,
state.selectionMode
]);
const onScroll = useCallback(
(data: { x: number; y: number; id?: string; route: string }) => {
@@ -93,88 +85,47 @@ export const Header = ({
};
}, [borderHidden, onScroll]);
return selectionMode && isFocused ? null : (
<>
const HeaderWrapper = hasSearch ? Pressable : View;
return (
<View
style={[
styles.container,
{
marginTop: Platform.OS === "android" ? insets.top : null,
backgroundColor: colors.primary.background,
overflow: "hidden",
borderBottomWidth: 1,
borderBottomColor: borderHidden
? "transparent"
: colors.secondary.background,
justifyContent: "space-between"
}
]}
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<HeaderWrapper
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: DefaultAppStyles.GAP_SMALL,
borderRadius: 10,
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
canGoBack={canGoBack}
onLeftButtonPress={onLeftMenuButtonPress}
/>
<Title
isHiddenOnRender={titleHiddenOnRender}
renderedInRoute={renderedInRoute}
id={id}
accentColor={accentColor}
title={title}
isBeta={isBeta}
/>
{hasSearch ? (
<Paragraph>
{selectionMode
? `${selectedItemsList.length} selected`
: strings.searchInRoute(title)}
</Paragraph>
) : (
<Heading size={SIZE.lg}>{title}</Heading>
)}
<RightMenus rightButton={rightButton} />
</HeaderWrapper>
</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 React from "react";
import { notesnook } from "../../../e2e/test.ids";
import { DDS } from "../../services/device-detection";
import Navigation from "../../services/navigation";
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";
export const LeftMenus = ({
@@ -41,7 +40,7 @@ export const LeftMenus = ({
if (onLeftButtonPress) return onLeftButtonPress();
if (!canGoBack) {
if (tabBarRef.current?.isDrawerOpen()) {
if (fluidTabsRef.current?.isDrawerOpen()) {
Navigation.closeDrawer();
} else {
Navigation.openDrawer();
@@ -54,23 +53,14 @@ export const LeftMenus = ({
return isTablet && !canGoBack ? null : (
<IconButton
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}
top={40}
right={DDS.isLargeTablet() ? 10 : 10}
onPress={_onLeftButtonPress}
onLongPress={() => {
Navigation.popToTop();
}}
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/>.
*/
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { Menu } from "react-native-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import {
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";
import React from "react";
import { StyleSheet, View } from "react-native";
import { IconButton, IconButtonProps } from "../ui/icon-button";
export const RightMenus = ({
headerRightButtons,
renderedInRoute,
id,
onPressDefaultRightButton,
search,
onSearch
rightButton
}: {
headerRightButtons?: HeaderRightButton[];
renderedInRoute?: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;
onSearch?: () => void;
rightButton?: IconButtonProps;
}) => {
const { colors } = useThemeColors();
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const deviceMode = useSettingStore((state) => state.deviceMode);
const menuRef = useRef<Menu>(null);
return (
<View style={styles.rightBtnContainer}>
{search ? (
<IconButton
onPress={onSearch}
testID="icon-search"
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
}}
{rightButton ? (
<IconButton {...rightButton} color={colors.primary.icon} />
) : (
<View
style={{
marginLeft: 10,
width: 32,
height: 32,
borderRadius: 100,
paddingHorizontal: 0,
borderWidth: 1,
borderColor: colors.primary.accent
width: 40,
height: 40
}}
/>
) : 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>
);
};
@@ -149,10 +52,6 @@ const styles = StyleSheet.create({
},
rightBtn: {
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
marginLeft: 10,
paddingRight: 0
alignItems: "center"
}
});

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/>.
*/
import { getFormattedDate } from "@notesnook/common";
import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import React from "react";
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 { IconButton } from "../../ui/icon-button";
import { DefaultAppStyles } from "../../../utils/styles";
import AppIcon from "../../ui/AppIcon";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { strings } from "@notesnook/intl";
export const NotebookHeader = ({
notebook,
onEditNotebook,
totalNotes = 0
}: {
notebook: Notebook;
onEditNotebook: () => void;
totalNotes: number;
}) => {
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 (
<View
style={{
marginBottom: 5,
padding: 0,
paddingHorizontal: DefaultAppStyles.GAP,
marginVertical: DefaultAppStyles.GAP,
backgroundColor: colors.secondary.background
}}
>
<View
style={{
width: "100%",
paddingVertical: 15,
paddingHorizontal: 12,
alignSelf: "center",
borderRadius: 10,
paddingTop: 25
gap: DefaultAppStyles.GAP_VERTICAL,
paddingVertical: 25
}}
>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{getFormattedDate(notebook.dateModified, "date-time")}
</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>
<AppIcon name="notebook" size={SIZE.xxl} />
<Heading size={SIZE.lg}>{notebook.title}</Heading>
{notebook.description ? (
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
@@ -140,17 +61,21 @@ export const NotebookHeader = ({
</Paragraph>
) : null}
<Paragraph
<View
style={{
marginTop: 10,
fontStyle: "italic",
fontFamily: undefined
flexDirection: "row",
gap: DefaultAppStyles.GAP_SMALL
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
<Paragraph size={SIZE.xxs} color={colors.secondary.paragraph}>
{strings.notes(totalNotes || 0)}
</Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxs}>
{getFormattedDate(notebook.dateModified, "date-time")}
</Paragraph>
</View>
</View>
</View>
);
};

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ import {
presentSheet
} from "../../../services/event-manager";
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 NotePreview from "../../note-history/preview";
@@ -66,7 +66,7 @@ export const openNote = async (
item: note
});
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)}
isSheet={isRenderedInActionSheet}
item={item}
index={index}
color={restProps.color?.colorCode}
>
<NoteItem {...restProps} item={item} index={index} isTrash={isTrash} />

View File

@@ -39,16 +39,15 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
style={{
position: "absolute",
width: "110%",
height: "150%",
backgroundColor: colors.selected.background,
borderLeftWidth: 5,
borderLeftColor: isEditingNote
? color
? color
: colors.selected.accent
: "transparent"
height: "100%",
backgroundColor: colors.selected.accent
// borderLeftWidth: 5,
// borderLeftColor: isEditingNote
// ? color
// ? color
// : colors.selected.accent
// : "transparent"
}}
collapsable={false}
/>
) : 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/>.
*/
import { Item, TrashItem } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { PropsWithChildren, useRef } from "react";
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 { DefaultAppStyles } from "../../../utils/styles";
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) {
if (useSelectionStore.getState().selectionMode === item.type) {
@@ -49,6 +49,7 @@ type SelectionWrapperProps = PropsWithChildren<{
testID?: string;
isSheet?: boolean;
color?: string;
index?: number;
}>;
const SelectionWrapper = ({
@@ -57,10 +58,16 @@ const SelectionWrapper = ({
testID,
isSheet,
children,
color
color,
index = 0
}: SelectionWrapperProps) => {
const itemId = useRef(item.id);
const { colors, isDark } = useThemeColors();
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
item.id
);
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
@@ -79,7 +86,13 @@ const SelectionWrapper = ({
return (
<Pressable
customColor={isSheet ? colors.secondary.background : "transparent"}
customColor={
isEditingNote
? colors.selected.background
: isSheet
? colors.primary.hover
: "transparent"
}
testID={testID}
onLongPress={onLongPress}
onPress={onPress}
@@ -91,15 +104,14 @@ const SelectionWrapper = ({
justifyContent: "space-between",
alignItems: "center",
width: "100%",
alignSelf: "center",
overflow: "hidden",
paddingHorizontal: 12,
paddingVertical: compactMode ? 4 : 12,
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: compactMode ? 4 : DefaultAppStyles.GAP_VERTICAL,
borderRadius: isSheet ? 10 : 0,
marginBottom: isSheet ? 12 : undefined
}}
>
{item.type === "note" ? <Filler item={item} color={color} /> : null}
<SelectionIcon item={item} />
{children}
</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/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Dimensions, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
import { getContainerBorder, hexToRGBA } from "../../utils/colors";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
@@ -38,19 +38,24 @@ export const Card = ({ color }: { color?: string }) => {
(announcements && announcements.length) ? null : (
<View
style={{
width: "95%"
width: "100%",
paddingHorizontal: DefaultAppStyles.GAP,
marginTop: DefaultAppStyles.GAP
}}
>
<Pressable
onPress={messageBoardState.onPress}
type="plain"
style={{
paddingVertical: 12,
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 0,
width: "100%"
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
width: "100%",
backgroundColor: colors.secondary.background,
borderWidth: 1,
borderColor: colors.primary.border
}}
>
<View
@@ -63,24 +68,14 @@ export const Card = ({ color }: { color?: string }) => {
<View
style={{
width: 40 * fontScale,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.error.accent, 0.15)
: hexToRGBA(color, 0.15),
height: 40 * fontScale,
borderRadius: 100,
alignItems: "center",
justifyContent: "center",
...getContainerBorder(
messageBoardState.type === "error"
? colors.error.accent
: color || colors.primary.accent,
0.4
)
justifyContent: "center"
}}
>
<Icon
size={SIZE.lg}
size={SIZE.xxxl}
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
@@ -95,9 +90,6 @@ export const Card = ({ color }: { color?: string }) => {
marginRight: 10
}}
>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{messageBoardState.message}
</Paragraph>
<Paragraph
style={{
flexWrap: "nowrap",
@@ -107,6 +99,9 @@ export const Card = ({ color }: { color?: string }) => {
>
{messageBoardState.actionText}
</Paragraph>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xxs}>
{messageBoardState.message}
</Paragraph>
</View>
</View>

View File

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

View File

@@ -26,7 +26,7 @@ import {
DraxListRenderItemContent
} from "react-native-drax";
import { tabBarRef } from "../../utils/global-refs";
import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button";
@@ -61,9 +61,9 @@ function ReorderableList<T extends { id: string }>({
const listRef = useRef<FlatList | null>(null);
if (dragging) {
tabBarRef.current?.lock();
fluidTabsRef.current?.lock();
} else {
tabBarRef.current?.unlock();
fluidTabsRef.current?.unlock();
}
useEffect(() => {
@@ -78,8 +78,7 @@ function ReorderableList<T extends { id: string }>({
return isHidden && !dragging ? null : (
<View
style={{
flexDirection: "row",
paddingHorizontal: 12
flexDirection: "row"
}}
>
<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
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 { Platform, View } from "react-native";
import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { ColorValues } from "../../utils/colors";
import { eOnLoadNote } from "../../utils/events";
import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { ReminderTime } from "../ui/reminder-time";
@@ -36,10 +37,8 @@ import Paragraph from "../ui/typography/paragraph";
import { DateMeta } from "./date-meta";
import { Items } from "./items";
import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { TagStrip, Tags } from "./tags";
import { tabBarRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
const Line = ({ top = 6, bottom = 6 }) => {
const { colors } = useThemeColors();
@@ -101,7 +100,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
style={{
flexDirection: "row",
alignItems: "center",
flexShrink: 1
flexShrink: 1,
gap: 5
}}
>
{item.type === "color" ? (
@@ -116,16 +116,15 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
marginRight: 10
}}
/>
) : item.type === "tag" ? (
<Icon
name="pound"
size={SIZE.lg}
color={colors.primary.icon}
/>
) : null}
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.primary.accent}>
#
</Heading>
) : null}
{item.title}
</Heading>
<Heading size={SIZE.lg}>{item.title}</Heading>
</View>
{item.type === "note" ? (
@@ -144,7 +143,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
newTab: true
});
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 ? (
<View
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;
let type = item?.type;
let props = [];
let android = [];
let dbItem;
switch (type) {
case "trash":
props[0] = item;
props.push(["delete", "restore"]);
dbItem = item;
break;
case "note":
android = Platform.OS === "android" ? ["pin-to-notifications"] : [];
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
]);
dbItem = await db.notes.note(item.id);
break;
case "notebook":
props[0] = await db.notebooks.notebook(item.id);
props.push([
"edit-notebook",
"pin",
"add-shortcut",
"trash",
"default-notebook",
"add-notebook",
"move-notes",
"move-notebook"
]);
dbItem = await db.notebooks.notebook(item.id);
break;
case "tag":
props[0] = await db.tags.tag(item.id);
props.push(["add-shortcut", "trash", "rename-tag"]);
dbItem = await db.tags.tag(item.id);
break;
case "color":
props[0] = await db.colors.color(item.id);
props.push([
"trash",
"rename-color",
...(useSideBarDraggingStore.getState().dragging ? [] : ["reorder"])
]);
dbItem = await db.colors.color(item.id);
break;
case "reminder": {
props[0] = await db.reminders.reminder(item.id);
props.push(["edit-reminder", "trash", "disable-reminder"]);
dbItem = await db.reminders.reminder(item.id);
break;
}
}
if (!props[0]) return;
presentSheet({
context: isSheet ? "local" : undefined,
component: (ref, close) => (
<Properties
close={() => {
close();
}}
actionSheetRef={ref}
item={props[0]}
buttons={props[1]}
/>
<Properties close={close} actionSheetRef={ref} item={dbItem} />
)
});
};

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 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 { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time";
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 }) => {
const { colors } = useThemeColors();
const user = useUserStore((state) => state.user);
const lastSynced = useUserStore((state) => state.lastSynced);
const dimensions = useWindowDimensions();
const shouldShrink = dimensions.fontScale > 1 && dimensions.width < 450;
return user && lastSynced >= item.dateModified ? (
<View
<Button
style={{
paddingVertical: 0,
width: "100%",
paddingHorizontal: 12,
alignItems: "center",
flexDirection: "row",
justifyContent: "flex-start",
alignSelf: "center",
paddingTop: 10,
marginTop: 10,
borderTopWidth: 1,
borderTopColor: colors.primary.border
alignSelf: "flex-start",
height: "auto",
padding: DefaultAppStyles.GAP_VERTICAL_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
}}
>
<Icon
name="shield-key-outline"
color={colors.primary.accent}
size={shouldShrink ? SIZE.xxl : SIZE.xxxl}
/>
<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
fontSize={SIZE.xxxs}
iconSize={SIZE.xs}
icon="shield-key-outline"
type="shade"
title="Encrypted and synced"
onPress={async () => {
try {
close();
@@ -100,11 +57,6 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
title={strings.learnMore()}
fontSize={SIZE.xs}
height={30}
type="secondaryAccented"
/>
</View>
) : null;
};

View File

@@ -35,18 +35,17 @@ import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { deleteItems } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { fluidTabsRef } from "../../utils/global-refs";
import { updateNotebook } from "../../utils/notebooks";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
export const SelectionHeader = React.memo(
({
@@ -75,9 +74,9 @@ export const SelectionHeader = React.memo(
useEffect(() => {
if (selectionMode) {
tabBarRef.current?.lock();
fluidTabsRef.current?.lock();
} else {
tabBarRef.current?.unlock();
fluidTabsRef.current?.unlock();
}
}, [selectionMode]);
@@ -139,61 +138,24 @@ export const SelectionHeader = React.memo(
<View
style={{
width: "100%",
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "android" ? insets.top : null,
backgroundColor: colors.primary.background,
justifyContent: "space-between",
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center",
flexDirection: "row",
zIndex: 999,
paddingHorizontal: 12
paddingHorizontal: DefaultAppStyles.GAP,
position: "absolute",
bottom: 0,
borderTopWidth: 1,
borderColor: colors.primary.border,
justifyContent: "space-between"
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
borderRadius: 100
}}
>
<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"
gap: DefaultAppStyles.GAP_SMALL
}}
>
<IconButton
@@ -202,42 +164,14 @@ export const SelectionHeader = React.memo(
.getState()
.setAll(allSelected ? [] : [...((await items?.ids()) || [])]);
}}
tooltipText="Select all"
tooltipPosition={4}
style={{
marginLeft: 10
}}
color={
allSelected ? colors.primary.accent : colors.primary.paragraph
}
size={SIZE.lg}
color={allSelected ? colors.primary.accent : colors.primary.icon}
name="select-all"
/>
{selectedItemsList.length ? (
<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}
/>
}
>
{[
{!selectedItemsList.length
? null
: [
{
title: strings.move(),
onPress: async () => {
@@ -341,20 +275,12 @@ export const SelectionHeader = React.memo(
}
].map((item) =>
!item.visible ? null : (
<Button
style={{
justifyContent: "flex-start",
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
<IconButton
size={SIZE.lg}
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}
icon={item.icon}
name={item.icon}
key={item.title}
title={item.title}
color={colors.primary.icon}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
@@ -364,9 +290,16 @@ export const SelectionHeader = React.memo(
/>
)
)}
</Menu>
) : null}
</View>
<IconButton
size={SIZE.lg}
onPress={() => {
clearSelection();
}}
color={colors.primary.icon}
name="close"
/>
</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 { strings } from "@notesnook/intl";
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
@@ -30,17 +31,16 @@ import {
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnNotebookUpdated } from "../../../utils/events";
import { getParentNotebookId } from "../../../utils/notebooks";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
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 = ({
notebook,
@@ -97,11 +97,10 @@ export const AddNotebookSheet = ({
parentNotebook?.id ||
(await getParentNotebookId(notebook?.id || (id as string)));
eSendEvent(eOnNotebookUpdated, parent);
if (notebook) {
setImmediate(() => {
eSendEvent(eOnNotebookUpdated, notebook.id);
});
eSendEvent(eOnNotebookUpdated, parent || notebook?.id);
if (!parent) {
useNotebookStore.getState().refresh();
}
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/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native";
@@ -45,7 +46,6 @@ import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { strings } from "@notesnook/intl";
const useNotebookExpandedStore = create<{
expanded: {
@@ -119,14 +119,19 @@ export const MoveNotebookSheet = ({
},
notebook
);
}
await db.relations.add(selectedNotebook, notebook);
if (parent) {
eSendEvent(eOnNotebookUpdated, parent);
}
await db.relations.add(selectedNotebook, notebook);
eSendEvent(eOnNotebookUpdated, selectedNotebook.id);
eSendEvent(eOnNotebookUpdated, notebook.id);
}
if (!parent) {
useNotebookStore.getState().refresh();
} else {
eSendEvent(eOnNotebookUpdated, selectedNotebook.id);
}
close?.();
}
});

View File

@@ -34,7 +34,7 @@ import { useDBItem, useNoteLocked } from "../../../hooks/use-db-item";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnLoadNote } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { fluidTabsRef } from "../../../utils/global-refs";
import { SIZE } from "../../../utils/size";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
@@ -423,7 +423,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
item: note,
blockId: blockId
});
tabBarRef.current?.goToPage(1);
fluidTabsRef.current?.goToPage(1);
close?.();
}}
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/>.
*/
import {
GroupingKey,
GroupOptions,
ItemType,
SortOptions
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import { View } from "react-native";
import { db } from "../../../common/database";
import { eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useThemeColors } from "@notesnook/theme";
import { RouteName } from "../../../stores/use-navigation-store";
import { GROUP, SORT } from "../../../utils/constants";
import { eGroupOptionsUpdated, refreshNotesPage } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import AppIcon from "../../ui/AppIcon";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import { strings } from "@notesnook/intl";
const Sort = ({ type, screen }) => {
const isTopicSheet = screen === "TopicSheet";
import Paragraph from "../../ui/typography/paragraph";
const Sort = ({ type, screen }: { type: ItemType; screen: RouteName }) => {
const { colors } = useThemeColors();
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 groupType = screen === "Notes" ? "home" : type + "s";
const updateGroupOptions = async (_groupOptions: GroupOptions) => {
const groupType =
screen === "Notes" ? "home" : ((type + "s") as GroupingKey);
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
Navigation.queueRoutesForUpdate(screen);
eSendEvent(eGroupOptionsUpdated, groupType);
eSendEvent(refreshNotesPage);
}, 1);
};
const setOrderBy = async () => {
let _groupOptions = {
const _groupOptions: GroupOptions = {
...groupOptions,
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
};
@@ -64,7 +74,8 @@ const Sort = ({ type, screen }) => {
style={{
width: "100%",
backgroundColor: colors.primary.background,
justifyContent: "space-between"
justifyContent: "space-between",
gap: DefaultAppStyles.GAP_SMALL
}}
>
<View
@@ -72,11 +83,12 @@ const Sort = ({ type, screen }) => {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 12
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<Heading
size={SIZE.xl}
size={SIZE.md}
style={{
alignSelf: "center"
}}
@@ -105,34 +117,22 @@ const Sort = ({ type, screen }) => {
? "sort-ascending"
: "sort-descending"
}
height={25}
height={30}
iconPosition="right"
fontSize={SIZE.sm - 1}
type="transparent"
buttonType={{
text: colors.primary.accent
}}
fontSize={SIZE.sm}
type="plain"
style={{
borderRadius: 100,
paddingHorizontal: 6
paddingHorizontal: DefaultAppStyles.GAP_SMALL
}}
onPress={setOrderBy}
/>
</View>
<Seperator />
<View
style={{
flexDirection: "row",
flexDirection: "column",
justifyContent: "flex-start",
flexWrap: "wrap",
borderBottomWidth: isTopicSheet ? 0 : 1,
borderBottomColor: colors.primary.border,
marginBottom: 12,
paddingHorizontal: 12,
paddingBottom: 12,
alignItems: "center"
borderBottomColor: colors.primary.border
}}
>
{groupOptions?.groupBy === "abc" ? (
@@ -149,105 +149,118 @@ const Sort = ({ type, screen }) => {
) : (
Object.keys(SORT).map((item) =>
(item === "dueDate" && screen !== "Reminders") ||
((screen !== "Tags" || screen !== "Reminders") &&
(screen !== "Tags" &&
screen !== "Reminders" &&
item === "dateModified") ||
((screen === "Tags" || screen === "Reminders") &&
item === "dateEdited") ? null : (
<Button
<Pressable
key={item}
type={groupOptions?.sortBy === item ? "selected" : "plain"}
title={strings.sortByStrings[item]()}
height={40}
iconPosition="left"
icon={groupOptions?.sortBy === item ? "check" : null}
noborder
style={{
marginRight: 10,
paddingHorizontal: 8
width: "100%",
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 () => {
let _groupOptions = {
const _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);
}}
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>
{isTopicSheet ? null : (
<>
<Heading
<View
style={{
marginLeft: 12
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
size={SIZE.lg}
>
{strings.groupBy()}
</Heading>
<Seperator />
<Heading size={SIZE.md}>{strings.groupBy()}</Heading>
</View>
<View
style={{
borderRadius: 0,
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 12
flexWrap: "wrap"
}}
>
{Object.keys(GROUP).map((item) => (
<Button
<Pressable
key={item}
testID={"btn-" + item}
type={
groupOptions?.groupBy === GROUP[item] ? "selected" : "plain"
groupOptions?.groupBy === GROUP[item as keyof typeof GROUP]
? "selected"
: "plain"
}
buttonType={{
text:
groupOptions?.groupBy === GROUP[item]
? colors.primary.accent
: colors.secondary.paragraph
noborder
style={{
width: "100%",
justifyContent: "space-between",
height: 40,
flexDirection: "row",
borderRadius: 0,
paddingHorizontal: DefaultAppStyles.GAP
}}
onPress={async () => {
let _groupOptions = {
const _groupOptions: GroupOptions = {
...groupOptions,
groupBy: GROUP[item]
sortBy:
type === "trash"
? "dateDeleted"
: (item as SortOptions["sortBy"])
};
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
await updateGroupOptions(_groupOptions);
}}
>
<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 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 { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { useEffect, useMemo } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { ColoredNotes } from "../../screens/notes/colored";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-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 { 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 { MenuItem } from "./menu-item";
export const ColorSection = React.memo(
function ColorSection() {
@@ -54,6 +50,54 @@ export const ColorSection = React.memo(
}
}, [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 (
<ReorderableList
onListOrderChanged={(data) => {
@@ -66,103 +110,16 @@ export const ColorSection = React.memo(
itemOrder={order}
hiddenItems={hiddensItems}
alwaysBounceVertical={false}
data={colorNotes}
data={menuItems}
style={{
width: "100%"
}}
showsVerticalScrollIndicator={false}
renderDraggableItem={({ item }) => {
return <ColorItem key={item.id} item={item} />;
return <MenuItem item={item} renderIcon={renderIcon} />;
}}
/>
);
},
() => 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 React, { useCallback } from "react";
import { View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import React from "react";
import { Image, View } from "react-native";
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 useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { eSendEvent } from "../../services/event-manager";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import Navigation from "../../services/navigation";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { MenuItemsList } from "../../utils/menu-items";
import ReorderableList from "../list/reorderable-list";
import { Button } from "../ui/button";
import { ColorSection } from "./color-section";
import { useSideBarDraggingStore } from "./dragging-store";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { UserStatus } from "./user-status";
import { strings } from "@notesnook/intl";
import { deleteItems } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { MoveNotebookSheet } from "../sheets/move-notebook";
import { UserSheet } from "../sheets/user";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { SideMenuHome } from "./side-menu-home";
import { SideMenuNotebooks } from "./side-menu-notebooks";
import { SideMenuTags } from "./side-menu-tags";
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(
function SideMenu() {
const { colors, isDark } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const subscriptionType = useUserStore(
(state) => state.user?.subscription?.type
);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const dragging = useSideBarDraggingStore((state) => state.dragging);
const [order, hiddensItems] = useMenuStore((state) => [
state.order["routes"],
state.hiddenItems["routes"]
const { colors } = useThemeColors();
const [index, setIndex] = React.useState(0);
const [routes] = React.useState<Route[]>([
{
key: "home",
title: "Home"
},
{
key: "notebooks",
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 (
<MenuItem
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
<SafeAreaView
style={{
height: "100%",
width: "100%",
flex: 1,
backgroundColor: colors.primary.background
}}
>
<View
style={{
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
paddingTop: insets.top
}}
>
<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
});
}}
<TabView
navigationState={{ index, routes }}
renderTabBar={(props) => <TabBar {...props} />}
tabBarPosition="bottom"
renderScene={renderScene}
onIndexChange={setIndex}
swipeEnabled={false}
animationEnabled={false}
/>
</View>
) : (
<UserStatus />
)}
</View>
</View>
) : null;
</SafeAreaView>
);
},
() => 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 React from "react";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
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 useNavigationStore from "../../stores/use-navigation-store";
import { SIZE, normalize } from "../../utils/size";
import { Button } from "../ui/button";
import { useFavoriteStore } from "../../stores/use-favorite-store";
import useNavigationStore, {
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 Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
function _MenuItem({
item,
index,
testID,
rightBtn
renderIcon
}: {
item: any;
index: number;
testID: string;
rightBtn?: {
name: string;
icon: string;
func: () => void;
};
item: SideMenuItem;
index?: number;
testID?: string;
renderIcon?: (item: SideMenuItem, size: number) => React.ReactNode;
}) {
const [itemCount, setItemCount] = useState(0);
const { colors } = useThemeColors();
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 = () => {
if (item.func) {
item.func();
} else {
if (useNavigationStore.getState().currentRoute !== item.name) {
Navigation.navigate(item.name, {
canGoBack: false,
beta: item.isBeta
if (item.onPress) return item.onPress(item);
if (useNavigationStore.getState().currentRoute !== item.id) {
Navigation.navigate(item.id as keyof RouteParams, {
canGoBack: false
});
}
}
if (item.close) {
setImmediate(() => {
Navigation.closeDrawer();
});
}
};
return (
<Pressable
testID={testID}
key={item.name + index}
key={item.id}
onPress={_onPress}
onLongPress={() => item.onLongPress?.(item)}
type={isFocused ? "selected" : "plain"}
style={{
width: "100%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 8,
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
justifyContent: "space-between",
alignItems: "center",
height: normalize(50),
marginBottom: 5
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL
}}
>
{renderIcon ? (
renderIcon(item, SIZE.md)
) : (
<Icon
style={{
width: 30,
textAlignVertical: "center",
textAlign: "left"
}}
@@ -106,66 +167,42 @@ function _MenuItem({
item.icon === "crown"
? colors.static.yellow
: isFocused
? colors.selected.icon
? colors.selected.paragraph
: 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 ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
<Paragraph
color={
isFocused ? colors.primary.paragraph : colors.secondary.paragraph
}
size={SIZE.sm}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
{strings.beta()}
{item.title}
</Paragraph>
</View>
) : null}
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}
/>
) : 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}
/>
{menuItemCount > 0 ? (
<Paragraph
size={SIZE.xxs}
color={
isFocused ? colors.primary.paragraph : colors.secondary.paragraph
}
>
{menuItemCount}
</Paragraph>
) : null}
</Pressable>
);
}
export const MenuItem = React.memo(_MenuItem, (prev, next) => {
if (prev.item.name !== next.item.name) return false;
if (prev.rightBtn?.name !== next.rightBtn?.name) return false;
if (
prev.item.id !== next.item.id &&
prev.item.data?.dateModified !== next.item.data?.dateModified
)
return false;
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 { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import NotebookScreen from "../../screens/notebook";
import { TaggedNotes } from "../../screens/notes/tagged";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-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 { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { Pressable } from "../ui/pressable";
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";
import { MenuItem } from "./menu-item";
import { useThemeColors } from "@notesnook/theme";
import { DefaultAppStyles } from "../../utils/styles";
export const PinnedSection = React.memo(
function PinnedSection() {
const { colors } = useThemeColors();
const menuPins = useMenuStore((state) => state.menuPins);
const loading = useSettingStore((state) => state.isAppLoading);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
@@ -52,42 +45,55 @@ export const PinnedSection = React.memo(
}
}, [loading, setMenuPins]);
const onPress = (item: Notebook | Tag) => {
if (item.type === "notebook") {
NotebookScreen.navigate(item);
} else if (item.type === "tag") {
TaggedNotes.navigate(item);
const onPress = React.useCallback((item: SideMenuItem) => {
const data = item.data as Notebook | Tag;
if (data.type === "notebook") {
NotebookScreen.navigate(data);
} else if (data.type === "tag") {
TaggedNotes.navigate(data);
}
setImmediate(() => {
Navigation.closeDrawer();
});
};
const renderItem = ({
item,
index
}: {
item: Notebook | Tag;
index: number;
}) => {
return <PinItem item={item} onPress={onPress} />;
};
}, []);
const menuItems = useMemo(
() =>
menuPins.map((item) => ({
id: item.id,
title: item.title,
icon: item.type === "notebook" ? "notebook-outline" : "pound",
dataType: item.type,
data: item,
onPress: onPress
})) as SideMenuItem[],
[menuPins, onPress]
);
const renderItem = React.useCallback(({ item }: { item: SideMenuItem }) => {
return <MenuItem item={item} />;
}, []);
return (
<View
style={{
flexGrow: 1
flexGrow: 1,
borderTopWidth: 1,
borderTopColor: colors.primary.border,
marginTop: DefaultAppStyles.GAP_SMALL,
paddingTop: DefaultAppStyles.GAP_SMALL
}}
>
<ReorderableList
onListOrderChanged={(data) => {
db.settings.setSideBarOrder("shortcuts", data);
}}
onHiddenItemsChanged={(data) => {}}
onHiddenItemsChanged={() => {}}
canHideItems={false}
itemOrder={order}
hiddenItems={[]}
alwaysBounceVertical={false}
data={menuPins}
data={menuItems}
style={{
flexGrow: 1,
width: "100%"
@@ -97,161 +103,9 @@ export const PinnedSection = React.memo(
}}
showsVerticalScrollIndicator={false}
renderDraggableItem={renderItem}
ListEmptyComponent={
<Notice
size="small"
type="information"
text={strings.sideMenuNotice()}
style={{
marginHorizontal: 12
}}
/>
}
/>
</View>
);
},
() => 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/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import React from "react";
@@ -30,13 +31,12 @@ import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { eOpenLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import { fluidTabsRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
export const UserStatus = () => {
const { colors, isDark } = useThemeColors();
@@ -71,7 +71,7 @@ export const UserStatus = () => {
<Pressable
onPress={() => {
Navigation.navigate("Settings");
tabBarRef.current.closeDrawer();
fluidTabsRef.current.closeDrawer();
}}
type="plain"
style={{
@@ -206,7 +206,7 @@ export const UserStatus = () => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
fluidTabsRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
}}

View File

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

View File

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

View File

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

View File

@@ -59,8 +59,7 @@ import {
import {
clearAppState,
editorController,
editorState,
setAppState
editorState
} from "../screens/editor/tiptap/utils";
import { useDragState } from "../screens/settings/editor/state";
import BackupService from "../services/backup";
@@ -103,7 +102,7 @@ import {
refreshNotesPage
} from "../utils/events";
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 { sleep } from "../utils/time";
@@ -168,7 +167,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
clearAppState();
editorState().movedAway = false;
eSendEvent(eOnLoadNote, { newNote: true });
tabBarRef.current?.goToPage(1, false);
fluidTabsRef.current?.goToPage(1, false);
return;
} else if (url.startsWith("https://notesnook.com/open_note")) {
const id = new URL(url).searchParams.get("id");
@@ -178,7 +177,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
eSendEvent(eOnLoadNote, {
item: note
});
tabBarRef.current?.goToPage(1, false);
fluidTabsRef.current?.goToPage(1, false);
}
}
} 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 {
Attachment,
Color,
HistorySession,
Note,
Notebook,
Reminder,
Shortcut,
Tag,
VirtualizedGrouping,
HistorySession
VirtualizedGrouping
} from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../common/database";
@@ -34,8 +34,8 @@ import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../services/event-manager";
import { eDBItemUpdate } from "../utils/events";
import { useSettingStore } from "../stores/use-setting-store";
import { eDBItemUpdate } from "../utils/events";
type ItemTypeKey = {
note: Note;
@@ -55,7 +55,8 @@ function isValidIdOrIndex(idOrIndex?: string | number) {
export const useDBItem = <T extends keyof ItemTypeKey>(
idOrIndex?: string | number,
type?: T,
items?: VirtualizedGrouping<ItemTypeKey[T]>
items?: VirtualizedGrouping<ItemTypeKey[T]>,
onItemUpdated?: (item?: ItemTypeKey[T]) => void
): [ItemTypeKey[T] | undefined, () => void] => {
const [item, setItem] = useState<ItemTypeKey[T]>();
const itemIdRef = useRef<string>();
@@ -67,15 +68,15 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
}
useEffect(() => {
const onUpdateItem = (itemId?: string) => {
const onUpdateItem = async (itemId?: string) => {
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return;
if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => {
setItem(item.item);
itemIdRef.current = item.item?.id;
});
const item = (await items.item(idOrIndex))?.item;
setItem(item);
itemIdRef.current = item?.id;
onItemUpdated?.(item);
} else {
if (!(db as any)[type + "s"][type]) {
console.warn(
@@ -83,12 +84,12 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
`db.${type}s.${type}(id: string)`
);
} else {
(db as any)[type + "s"]
?.[type]?.(idOrIndex as string)
.then((item: ItemTypeKey[T]) => {
const item = await (db as any)[type + "s"]?.[type]?.(
idOrIndex as string
);
setItem(item);
itemIdRef.current = item.id;
});
onItemUpdated?.(item);
}
}
};
@@ -110,7 +111,7 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
return () => {
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
};
}, [idOrIndex, type, items]);
}, [idOrIndex, type, items, onItemUpdated]);
return [
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/>.
*/
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 { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { eGroupOptionsUpdated, eOnNotebookUpdated } from "../utils/events";
@@ -26,17 +26,20 @@ import { useDBItem, useTotalNotes } from "./use-db-item";
export const useNotebook = (
id?: string | number,
items?: VirtualizedGrouping<Notebook>,
nestedNotebooks?: boolean
nestedNotebooks?: boolean,
countNotes?: boolean
) => {
const [item, refresh] = useDBItem(id, "notebook", items);
const groupOptions = db.settings.getGroupOptions("notebooks");
const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>();
const { totalNotes: nestedNotebookNotesCount, getTotalNotes } =
useTotalNotes("notebook");
const getTotalNotesRef = useRef(getTotalNotes);
getTotalNotesRef.current = getTotalNotes;
const onItemUpdated = React.useCallback(
(item?: Notebook) => {
if (!item) return;
const onRequestUpdate = React.useCallback(() => {
if (!item?.id) return;
if (nestedNotebooks) {
const selector = db.relations.from(
{
type: "notebook",
@@ -44,9 +47,8 @@ export const useNotebook = (
},
"notebook"
).selector;
selector.ids().then((notebookIds) => {
getTotalNotes(notebookIds);
getTotalNotesRef.current(notebookIds);
});
selector
@@ -54,28 +56,31 @@ export const useNotebook = (
.then((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(() => {
const onNotebookUpdate = (id?: string) => {
if (typeof id === "string" && id !== id) return;
setImmediate(() => {
if (nestedNotebooks) {
onRequestUpdate();
}
refresh();
});
refreshRef.current();
};
const onUpdate = (type: string) => {
if (type !== "notebooks") return;
onRequestUpdate();
refreshRef.current();
};
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
@@ -84,13 +89,14 @@ export const useNotebook = (
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
};
}, [onRequestUpdate, item?.id, refresh, nestedNotebooks]);
}, [nestedNotebooks]);
return {
notebook: item,
nestedNotebookNotesCount,
nestedNotebooks: item ? notebooks : undefined,
onUpdate: onRequestUpdate,
groupOptions
onUpdate: () => refresh(),
groupOptions,
notesCount: !item ? 0 : nestedNotebookNotesCount(item?.id)
};
};

View File

@@ -44,7 +44,7 @@ export const useShortcutManager = ({
shortcuts = defaultShortcuts
}: {
onShortcutPressed: (shortcut: ShortcutItem | null) => void;
shortcuts: ShortcutItem[];
shortcuts?: ShortcutItem[];
}) => {
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 */
import { i18n } from "@lingui/core";
import { strings } from "@notesnook/intl";
import React, {
forwardRef,
useCallback,
@@ -48,7 +46,6 @@ import {
eUnlockWithPassword
} from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types";
@@ -61,6 +58,9 @@ import {
openInternalLink,
randId
} from "./tiptap/utils";
import { fluidTabsRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import { i18n } from "@lingui/core";
const style: ViewStyle = {
height: "100%",
@@ -348,7 +348,7 @@ const useLockedNoteHandler = () => {
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) {
if (tabRef.current?.session?.locked && fluidTabsRef.current?.page() === 2) {
unlock();
}
return () => {

View File

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

View File

@@ -61,7 +61,7 @@ import {
eShowMergeDialog,
eUpdateNoteInEditor
} from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { fluidTabsRef } from "../../../utils/global-refs";
import { sleep } from "../../../utils/time";
import { unlockVault } from "../../../utils/unlock-vault";
import { onNoteCreated } from "../../notes/common";
@@ -995,14 +995,14 @@ export const useEditor = (
if (!appState) return;
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
if (tabBarRef.current?.page() === 2) {
if (fluidTabsRef.current?.page() === 2) {
state.current.movedAway = false;
}
if (!state.current.editorStateRestored) {
state.current.isRestoringState = true;
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1, false);
fluidTabsRef.current?.goToPage(1, false);
}
}
@@ -1081,12 +1081,12 @@ export const useEditor = (
item: note
});
}
tabBarRef.current?.goToPage(1);
fluidTabsRef.current?.goToPage(1);
} else {
noteId = useTabStore.getState().getCurrentNoteId() || null;
if (!noteId) {
loadNote({ newNote: true });
if (tabBarRef.current?.page() === 1) {
if (fluidTabsRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} 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/>.
*/
import { strings } from "@notesnook/intl";
import React from "react";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
@@ -29,7 +30,7 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotes } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common";
import { strings } from "@notesnook/intl";
import { FilterBar } from "./filter-bar";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes();
@@ -66,6 +67,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
id={route.name}
onPressDefaultRightButton={openEditor}
/>
<FilterBar />
<DelayLayout wait={loading}>
<List
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/>.
*/
import { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core";
import { Note, Notebook, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import { Properties } from "../../components/properties";
import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { IconButton } from "../../components/ui/icon-button";
@@ -40,8 +42,8 @@ import useNavigationStore, {
import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { SIZE } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { openEditor, setOnFirstSave } from "../notes/common";
import { strings } from "@notesnook/intl";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
@@ -125,6 +127,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
setNotes(notes);
await notes.item(0, resolveItems);
syncWithNavigation();
} else {
Navigation.goBack();
}
setLoading(false);
} catch (e) {
@@ -160,13 +164,18 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
renderedInRoute={route.name}
title={params.current.item?.title}
canGoBack={params?.current?.canGoBack}
rightButton={{
name: "dots-vertical",
onPress: () => {
Properties.present(params.current.item);
}
}}
hasSearch={true}
onSearch={() => {
const selector = db.relations.from(
params.current.item,
"note"
).selector;
Navigation.push("Search", {
placeholder: strings.searchInRoute(params.current.item?.title),
type: "note",
@@ -175,19 +184,18 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
items: selector
});
}}
titleHiddenOnRender
id={params.current.item?.id}
onPressDefaultRightButton={openEditor}
/>
{breadcrumbs && breadcrumbs.length > 0 ? (
<View
style={{
width: "100%",
paddingHorizontal: 12,
paddingHorizontal: DefaultAppStyles.GAP,
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap"
flexWrap: "wrap",
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
>
<IconButton
@@ -260,6 +268,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
}}
/>
</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 { eOnLoadNote, eOnNotebookUpdated } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { fluidTabsRef } from "../../utils/global-refs";
import { editorState } from "../editor/tiptap/utils";
export const PLACEHOLDER_DATA = {
@@ -58,7 +58,7 @@ export function openEditor() {
eSendEvent(eOnLoadNote, { newNote: true });
editorState().currentlyEditing = true;
editorState().movedAway = false;
tabBarRef.current?.goToPage(1);
fluidTabsRef.current?.goToPage(1);
} else {
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/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import { Platform, View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
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 { DDS } from "../../services/device-detection";
import { useSelectionStore } from "../../stores/use-selection-store";
import Navigation from "../../services/navigation";
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 = ({
onChangeText,
loading
@@ -50,13 +50,21 @@ export const SearchBar = ({
return selectionMode && isFocused ? null : (
<View
style={{
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
width: "100%",
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: Platform.OS === "ios" ? 0 : insets.top + 5
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
flexShrink: 1,
width: "100%",
paddingHorizontal: 12
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
borderRadius: 10,
borderColor: colors.primary.border,
borderWidth: 1
}}
>
<IconButton
@@ -69,22 +77,17 @@ export const SearchBar = ({
}}
color={colors.primary.paragraph}
type="plain"
style={{
paddingLeft: 0,
marginLeft: -5,
marginRight: DDS.isLargeTablet() ? 10 : 7
}}
/>
<TextInput
ref={inputRef}
testID="search-input"
style={{
fontSize: SIZE.md + 1,
fontSize: SIZE.sm,
fontFamily: "OpenSans-Regular",
flexGrow: 1,
height: "100%",
color: colors.primary.paragraph
color: colors.primary.paragraph,
height: 40
}}
autoFocus
onChangeText={_onChangeText}
@@ -97,5 +100,6 @@ export const SearchBar = ({
placeholderTextColor={colors.primary.placeholder}
/>
</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 React, { useEffect } from "react";
import React from "react";
import { View } from "react-native";
import { KeyboardAwareFlatList } from "react-native-keyboard-aware-scroll-view";
import Animated, { FadeInDown } from "react-native-reanimated";
@@ -26,7 +26,6 @@ import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import useNavigationStore from "../../stores/use-navigation-store";
import { tabBarRef } from "../../utils/global-refs";
import { components } from "./components";
import { SectionItem } from "./section-item";
import { RouteParams, SettingSection } from "./types";
@@ -42,16 +41,10 @@ const Group = ({
}: NativeStackScreenProps<RouteParams, "SettingsGroup">) => {
useNavigationFocus(navigation, {
onFocus: () => {
tabBarRef.current?.lock();
useNavigationStore.getState().setFocusedRouteId("Settings");
return false;
}
});
useEffect(() => {
return () => {
tabBarRef.current?.unlock();
};
}, []);
const renderItem = ({ item }: { item: SettingSection; index: number }) => (
<SectionItem item={item} />
);

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ import { useReminderStore } from "../stores/use-reminder-store";
import { useTagStore } from "../stores/use-tag-store";
import { useTrashStore } from "../stores/use-trash-store";
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";
/**
@@ -122,30 +122,36 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
}
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() {
rootNavigatorRef.current?.goBack();
appNavigatorRef.current?.goBack();
}
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]) {
rootNavigatorRef.current?.dispatch(StackActions.replace(screen, params));
console.log(`Navigation.replace ${screen} route`);
appNavigatorRef.current?.dispatch(
StackActions.replace(screen as string, params)
);
}
function popToTop() {
rootNavigatorRef.current?.dispatch(StackActions.popToTop());
console.log(`Navigation.popToTop`);
appNavigatorRef.current?.dispatch(StackActions.popToTop());
}
function openDrawer() {
tabBarRef.current?.openDrawer();
fluidTabsRef.current?.openDrawer();
}
function closeDrawer() {
tabBarRef.current?.closeDrawer();
fluidTabsRef.current?.closeDrawer();
}
const Navigation = {

View File

@@ -44,7 +44,7 @@ import { useReminderStore } from "../stores/use-reminder-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useUserStore } from "../stores/use-user-store";
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 { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
@@ -440,7 +440,7 @@ async function loadNote(id: string, jump: boolean) {
const note = await db.notes.note(id);
if (!note) return;
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
fluidTabsRef.current?.goToPage(1);
}
NotesnookModule.setAppState(
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;
getSelectedItemIds: () => string[];
getDeselectedItemIds: () => string[];
selectAll?: () => void;
}
export function createItemSelectionStore(

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ import { presentDialog } from "../components/dialog/functions";
import { eSendEvent, ToastManager } from "../services/event-manager";
import Navigation from "../services/navigation";
import { useMenuStore } from "../stores/use-menu-store";
import { useNotebookStore } from "../stores/use-notebook-store";
import { useRelationStore } from "../stores/use-relation-store";
import { useTagStore } from "../stores/use-tag-store";
import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events";
@@ -72,8 +73,10 @@ async function deleteNotebook(id: string, deleteNotes: boolean) {
}
}
await db.notebooks.moveToTrash(id);
if (parentId) {
eSendEvent(eOnNotebookUpdated, parentId);
if (!parentId) {
useNotebookStore.getState().refresh();
}
}
@@ -113,7 +116,6 @@ export const deleteItems = async (
if (!result.delete) return;
for (const id of itemIds) {
await deleteNotebook(id, result.deleteNotes);
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
}
} else if (type === "tag") {
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 { createRef } from "react";
import { TextInput, View } from "react-native";
import { TabsRef } from "../components/tabs";
import { TabsRef } from "../components/fluid-panels";
import { RouteParams } from "../stores/use-navigation-store";
export const inputRef = createRef<TextInput>();
export const rootNavigatorRef = createNavigationContainerRef<RouteParams>();
export const tabBarRef = createRef<TabsRef>();
export const rootNavigatorRef = createNavigationContainerRef<{
FluidPanelsView: undefined;
AppLock: undefined;
Settings: undefined;
}>();
export const appNavigatorRef = createNavigationContainerRef<RouteParams>();
export const fluidTabsRef = createRef<TabsRef>();
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/>.
*/
import { Item, ItemType } from "@notesnook/core";
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",
name: "Notes",
icon: "home-variant-outline",
close: true
dataType: "note",
id: "Notes",
title: "Notes",
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",
name: "Notebooks",
icon: "book-outline",
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",
dataType: "monograph",
id: "Monographs",
title: "Monographs",
icon: "text-box-multiple-outline",
close: true,
func: () => {
onPress: () => {
Monographs.navigate();
}
},
{
id: "trash",
name: "Trash",
icon: "delete-outline",
close: true
dataType: "note",
id: "Archive",
title: "Archive",
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 { eSendEvent } from "../services/event-manager";
import { useNotebookStore } from "../stores/use-notebook-store";
import { eOnNotebookUpdated } from "./events";
export async function findRootNotebookId(id: string) {
@@ -51,9 +52,14 @@ export async function getParentNotebookId(id: string) {
return relation?.[0]?.fromId;
}
export async function updateNotebook(id?: string) {
export async function updateNotebook(id?: string, updateParent?: boolean) {
eSendEvent(eOnNotebookUpdated, id);
if (id) {
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
if (updateParent && id) {
const parent = await getParentNotebookId(id);
if (parent) {
eSendEvent(eOnNotebookUpdated, parent);
} else {
useNotebookStore.getState().refresh();
}
}
}

View File

@@ -81,31 +81,33 @@ export const normalize = (size) => {
return correction(size, 1);
}
};
export const SIZE = {
xxs: normalize(11) * scale.fontScale,
xs: normalize(12.5) * scale.fontScale,
function getSize() {
return {
xxxs: normalize(11) * scale.fontScale,
xxs: normalize(12.5) * scale.fontScale,
xs: normalize(14) * scale.fontScale,
sm: normalize(15) * scale.fontScale,
md: normalize(16.5) * scale.fontScale,
lg: normalize(22) * scale.fontScale,
xl: normalize(24) * scale.fontScale,
xxl: normalize(28) * scale.fontScale,
xxxl: normalize(32) * scale.fontScale
};
};
}
export const SIZE = getSize();
export function updateSize() {
SIZE.xxs = normalize(11) * scale.fontScale;
SIZE.xs = normalize(12.5) * scale.fontScale;
SIZE.sm = normalize(15) * scale.fontScale;
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;
const newSize = getSize();
for (const key in SIZE) {
SIZE[key] = newSize[key];
}
ph = 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 pv = normalize(10); // padding vertical
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import DialogProvider from "../components/dialog-provider";
import { Toast } from "../components/toast";
import { TabHolder } from "./tabs-holder";
import { ScopedThemeProvider } from "@notesnook/theme";
const _ApplicationHolder = () => {
return (
<>
<TabHolder />
<ScopedThemeProvider value="dialog">
<Toast />
</ScopedThemeProvider>
<DialogProvider />
</>
);
export const DefaultAppStyles = {
GAP: 16,
GAP_SMALL: 8,
GAP_VERTICAL: 6,
GAP_VERTICAL_SMALL: 4
};
export const ApplicationHolder = React.memo(_ApplicationHolder, () => true);

View File

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

View File

@@ -73,7 +73,9 @@
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@ammarahmed/react-native-share-extension": "^2.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": {
"detox": "^20.27.6",
@@ -88,6 +90,7 @@
"@tsconfig/react-native": "^3.0.2",
"@types/html-to-text": "^8.0.1",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.2.39",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.0.31",
"version": "3.0.32",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.0.31",
"version": "3.0.32",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -14,7 +14,6 @@
"app/"
],
"dependencies": {
"@ammarahmed/react-native-share-extension": "^2.9.0",
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",
@@ -3728,6 +3727,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",
@@ -7743,7 +7743,7 @@
},
"../../packages/editor-mobile/node_modules/@types/prop-types": {
"version": "15.7.11",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/q": {
@@ -7763,7 +7763,7 @@
},
"../../packages/editor-mobile/node_modules/@types/react": {
"version": "18.2.39",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -7794,7 +7794,7 @@
},
"../../packages/editor-mobile/node_modules/@types/scheduler": {
"version": "0.16.8",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/semver": {
@@ -12619,7 +12619,7 @@
},
"../../packages/editor-mobile/node_modules/immer": {
"version": "9.0.21",
"devOptional": true,
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -23091,6 +23091,7 @@
},
"../../packages/editor/node_modules/js-tokens": {
"version": "4.0.0",
"dev": true,
"license": "MIT"
},
"../../packages/editor/node_modules/jsesc": {
@@ -23141,6 +23142,7 @@
},
"../../packages/editor/node_modules/loose-envify": {
"version": "1.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -23652,6 +23654,7 @@
},
"../../packages/editor/node_modules/react": {
"version": "18.3.1",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -23670,6 +23673,7 @@
},
"../../packages/editor/node_modules/react-dom": {
"version": "18.3.1",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -23808,6 +23812,7 @@
},
"../../packages/editor/node_modules/scheduler": {
"version": "0.23.2",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -28315,6 +28320,7 @@
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@ammarahmed/react-native-share-extension": "^2.9.0",
"@ammarahmed/react-native-sodium": "^1.6.1",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^4.1.1",
@@ -28357,6 +28363,7 @@
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation": "github:yamill/react-native-orientation",
"react-native-pager-view": "^6.5.1",
"react-native-pdf": "6.6.2",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-quick-sqlite": "^8.0.6",
@@ -28369,6 +28376,7 @@
"react-native-share": "^12.0.3",
"react-native-svg": "^12.3.0",
"react-native-swiper-flatlist": "3.2.2",
"react-native-tab-view": "^4.0.2",
"react-native-theme-switch-animation": "^0.6.0",
"react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0",
@@ -28390,6 +28398,7 @@
"@types/html-to-text": "^8.0.1",
"@types/jest": "^29.5.14",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.2.39",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",
@@ -28590,6 +28599,7 @@
},
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
@@ -28709,6 +28719,7 @@
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
@@ -28968,6 +28979,7 @@
"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",
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
},
@@ -28982,6 +28994,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",
"integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^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",
"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==",
"dev": true,
"engines": {
"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",
"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.",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
@@ -29176,6 +29191,7 @@
},
"node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.12.13"
@@ -29188,6 +29204,7 @@
"version": "7.14.5",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
@@ -29225,6 +29242,7 @@
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
"integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.3"
},
@@ -29249,6 +29267,7 @@
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz",
"integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -29263,6 +29282,7 @@
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz",
"integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -29277,6 +29297,7 @@
"version": "7.10.4",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
@@ -29288,6 +29309,7 @@
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
@@ -29387,6 +29409,7 @@
"version": "7.14.5",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
@@ -29414,6 +29437,7 @@
"version": "7.18.6",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
@@ -29442,6 +29466,7 @@
"version": "7.25.4",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5",
@@ -29472,6 +29497,7 @@
},
"node_modules/@babel/plugin-transform-block-scoped-functions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29500,6 +29526,7 @@
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz",
"integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==",
"dev": true,
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29515,6 +29542,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",
"integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==",
"dev": true,
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5",
@@ -29579,6 +29607,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz",
"integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29594,6 +29623,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz",
"integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -29608,6 +29638,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
},
@@ -29620,6 +29651,7 @@
},
"node_modules/@babel/plugin-transform-exponentiation-operator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
@@ -29636,6 +29668,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",
"integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
@@ -29663,6 +29696,7 @@
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29693,6 +29727,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz",
"integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-json-strings": "^7.8.3"
@@ -29721,6 +29756,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",
"integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
@@ -29734,6 +29770,7 @@
},
"node_modules/@babel/plugin-transform-member-expression-literals": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29749,6 +29786,7 @@
"version": "7.24.7",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29779,6 +29817,7 @@
"version": "7.25.0",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-module-transforms": "^7.22.5",
@@ -29796,6 +29835,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz",
"integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==",
"dev": true,
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -29825,6 +29865,7 @@
"version": "7.24.7",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -29854,6 +29895,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz",
"integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
@@ -29869,6 +29911,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",
"integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.22.5",
"@babel/helper-compilation-targets": "^7.22.5",
@@ -29885,6 +29928,7 @@
},
"node_modules/@babel/plugin-transform-object-super": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -29901,6 +29945,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",
"integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
@@ -29974,6 +30019,7 @@
},
"node_modules/@babel/plugin-transform-property-literals": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30043,6 +30089,7 @@
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -30059,6 +30106,7 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz",
"integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -30151,6 +30199,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz",
"integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -30181,6 +30230,7 @@
"version": "7.24.7",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
@@ -30195,6 +30245,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",
"integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30224,6 +30275,7 @@
"version": "7.25.4",
"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==",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30239,6 +30291,7 @@
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz",
"integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.22.5",
"@babel/helper-compilation-targets": "^7.22.5",
@@ -30332,6 +30385,7 @@
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz",
"integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
@@ -30347,6 +30401,7 @@
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -33650,7 +33705,8 @@
"node_modules/@types/estree": {
"version": "1.0.5",
"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": {
"version": "4.1.6",
@@ -33756,14 +33812,14 @@
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -34119,6 +34175,7 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
"dev": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@@ -34127,22 +34184,26 @@
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"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": {
"version": "1.11.6",
"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": {
"version": "1.12.1",
"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": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
@@ -34152,12 +34213,14 @@
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"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": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@@ -34169,6 +34232,7 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -34177,6 +34241,7 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@@ -34184,12 +34249,14 @@
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"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": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@@ -34205,6 +34272,7 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@@ -34217,6 +34285,7 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
@@ -34228,6 +34297,7 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
@@ -34241,6 +34311,7 @@
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
"dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2"
@@ -34298,12 +34369,14 @@
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"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": {
"version": "4.2.2",
"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": {
"version": "1.1.0",
@@ -35302,6 +35375,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"engines": {
"node": ">=6.0"
}
@@ -35732,7 +35806,7 @@
},
"node_modules/csstype": {
"version": "3.1.2",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/date-fns": {
@@ -36332,6 +36406,7 @@
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -36451,7 +36526,8 @@
"node_modules/es-module-lexer": {
"version": "1.5.4",
"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": {
"version": "1.0.0",
@@ -36796,6 +36872,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
@@ -36808,6 +36885,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"engines": {
"node": ">=4.0"
}
@@ -36971,6 +37049,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -36982,6 +37061,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
@@ -36990,6 +37070,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -37749,7 +37830,8 @@
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"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": {
"version": "4.4.0",
@@ -39838,7 +39920,8 @@
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"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": {
"version": "1.0.1",
@@ -40314,6 +40397,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"engines": {
"node": ">=6.11.5"
}
@@ -42543,6 +42627,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -42564,30 +42649,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"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": {
"version": "1.0.3",
"license": "MIT",
@@ -43023,6 +43084,15 @@
"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": {
"version": "6.6.2",
"license": "MIT",
@@ -43187,6 +43257,27 @@
"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": {
"version": "0.6.0",
"license": "MIT",
@@ -43507,6 +43598,7 @@
},
"node_modules/regenerator-transform": {
"version": "0.15.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.4"
@@ -43859,6 +43951,7 @@
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"randombytes": "^2.1.0"
@@ -44516,6 +44609,7 @@
},
"node_modules/tapable": {
"version": "2.2.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -44593,6 +44687,7 @@
"version": "5.3.10",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"jest-worker": "^27.4.5",
@@ -44626,6 +44721,7 @@
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -44639,6 +44735,7 @@
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -45215,6 +45312,7 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -45238,6 +45336,7 @@
"version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
@@ -45353,6 +45452,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"engines": {
"node": ">=10.13.0"
}

View File

@@ -83,7 +83,16 @@ const EXTRA_ICON_NAMES = [
"arrow-up-bold",
"login",
"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);

View File

@@ -63,6 +63,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.1.1",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@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`,
fontLigaturesDesc: () =>
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": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/css/-/css-0.16.1.tgz",
"integrity": "sha512-8TO2DbiqPrRyTlGRIElDak/p0M4ykyd8LkeavyOF/sTE9s93AwyFcle6KYYMEULrJP49SyYiEvTif7J7Z50DhA==",
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/css/-/css-0.16.2.tgz",
"integrity": "sha512-fNe+FhwKC5+7jQfxCRnm3oqYNhMFuiWiLA9OoLBEkt3b4egot29UK1+fqemwiNVjl206e2fPT5Z7uXRdb6LC2A==",
"dev": true,
"dependencies": {
"csstype": "^3.0.10"