mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
mobile: push changes
This commit is contained in:
committed by
Abdullah Atta
parent
1a61c4ee8f
commit
ceb6e94d0c
@@ -108,7 +108,7 @@ class RNSqliteConnection implements DatabaseConnection {
|
|||||||
: "exec";
|
: "exec";
|
||||||
const result = await this.db.executeAsync(sql, parameters as any[]);
|
const result = await this.db.executeAsync(sql, parameters as any[]);
|
||||||
|
|
||||||
console.log("SQLITE result:", result?.rows?._array);
|
// console.log("SQLITE result:", result?.rows?._array);
|
||||||
if (mode === "query" || !result.insertId)
|
if (mode === "query" || !result.insertId)
|
||||||
return {
|
return {
|
||||||
rows: result.rows?._array || []
|
rows: result.rows?._array || []
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { KeyboardAvoidingView, Platform } from "react-native";
|
|||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { Header } from "../header";
|
|
||||||
import SelectionHeader from "../selection-header";
|
import SelectionHeader from "../selection-header";
|
||||||
|
|
||||||
export const Container = ({ children }: PropsWithChildren) => {
|
export const Container = ({ children }: PropsWithChildren) => {
|
||||||
@@ -46,7 +45,6 @@ export const Container = ({ children }: PropsWithChildren) => {
|
|||||||
{!introCompleted ? null : (
|
{!introCompleted ? null : (
|
||||||
<>
|
<>
|
||||||
<SelectionHeader />
|
<SelectionHeader />
|
||||||
<Header />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -20,39 +20,70 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Platform, StyleSheet, View } from "react-native";
|
import { Platform, StyleSheet, View } from "react-native";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import { SearchBar } from "../../screens/search/search-bar";
|
|
||||||
import {
|
import {
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
|
||||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { eScrollEvent } from "../../utils/events";
|
import { eScrollEvent } from "../../utils/events";
|
||||||
import { LeftMenus } from "./left-menus";
|
import { LeftMenus } from "./left-menus";
|
||||||
import { RightMenus } from "./right-menus";
|
import { RightMenus } from "./right-menus";
|
||||||
import { Title } from "./title";
|
import { Title } from "./title";
|
||||||
|
import useNavigationStore, {
|
||||||
|
RouteName
|
||||||
|
} from "../../stores/use-navigation-store";
|
||||||
|
|
||||||
const _Header = () => {
|
type HeaderRightButton = {
|
||||||
|
title: string;
|
||||||
|
onPress: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = ({
|
||||||
|
renderedInRoute,
|
||||||
|
onLeftMenuButtonPress,
|
||||||
|
title,
|
||||||
|
titleHiddenOnRender,
|
||||||
|
headerRightButtons,
|
||||||
|
id,
|
||||||
|
accentColor,
|
||||||
|
isBeta,
|
||||||
|
canGoBack,
|
||||||
|
onPressDefaultRightButton,
|
||||||
|
hasSearch,
|
||||||
|
onSearch
|
||||||
|
}: {
|
||||||
|
onLeftMenuButtonPress?: () => void;
|
||||||
|
renderedInRoute: RouteName;
|
||||||
|
id?: string;
|
||||||
|
title: string;
|
||||||
|
headerRightButtons?: HeaderRightButton[];
|
||||||
|
titleHiddenOnRender?: boolean;
|
||||||
|
accentColor?: string;
|
||||||
|
isBeta?: boolean;
|
||||||
|
canGoBack?: boolean;
|
||||||
|
onPressDefaultRightButton?: () => void;
|
||||||
|
hasSearch?: boolean;
|
||||||
|
onSearch?: () => void;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const [hide, setHide] = useState(true);
|
const [borderHidden, setBorderHidden] = useState(true);
|
||||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const currentScreen = useNavigationStore(
|
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
|
||||||
(state) => state.currentScreen?.name
|
|
||||||
);
|
|
||||||
|
|
||||||
const onScroll = useCallback(
|
const onScroll = useCallback(
|
||||||
(data: { x: number; y: number }) => {
|
(data: { x: number; y: number; id?: string; route: string }) => {
|
||||||
|
if (data.route !== renderedInRoute || data.id !== id) return;
|
||||||
if (data.y > 150) {
|
if (data.y > 150) {
|
||||||
if (!hide) return;
|
if (!borderHidden) return;
|
||||||
setHide(false);
|
setBorderHidden(false);
|
||||||
} else {
|
} else {
|
||||||
if (hide) return;
|
if (borderHidden) return;
|
||||||
setHide(true);
|
setBorderHidden(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hide]
|
[borderHidden, id, renderedInRoute]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -60,9 +91,9 @@ const _Header = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eScrollEvent, onScroll);
|
eUnSubscribeEvent(eScrollEvent, onScroll);
|
||||||
};
|
};
|
||||||
}, [hide, onScroll]);
|
}, [borderHidden, onScroll]);
|
||||||
|
|
||||||
return selectionMode ? null : (
|
return selectionMode && isFocused ? null : (
|
||||||
<>
|
<>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -72,29 +103,42 @@ const _Header = () => {
|
|||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: hide
|
borderBottomColor: borderHidden
|
||||||
? "transparent"
|
? "transparent"
|
||||||
: colors.secondary.background,
|
: colors.secondary.background,
|
||||||
justifyContent: "space-between"
|
justifyContent: "space-between"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentScreen === "Search" ? (
|
<>
|
||||||
<SearchBar />
|
<View style={styles.leftBtnContainer}>
|
||||||
) : (
|
<LeftMenus
|
||||||
<>
|
canGoBack={canGoBack}
|
||||||
<View style={styles.leftBtnContainer}>
|
onLeftButtonPress={onLeftMenuButtonPress}
|
||||||
<LeftMenus />
|
/>
|
||||||
<Title />
|
|
||||||
</View>
|
<Title
|
||||||
<RightMenus />
|
isHiddenOnRender={titleHiddenOnRender}
|
||||||
</>
|
renderedInRoute={renderedInRoute}
|
||||||
)}
|
id={id}
|
||||||
|
accentColor={accentColor}
|
||||||
|
title={title}
|
||||||
|
isBeta={isBeta}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<RightMenus
|
||||||
|
renderedInRoute={renderedInRoute}
|
||||||
|
id={id}
|
||||||
|
headerRightButtons={headerRightButtons}
|
||||||
|
onPressDefaultRightButton={onPressDefaultRightButton}
|
||||||
|
search={hasSearch}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const Header = React.memo(_Header, () => true);
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|||||||
@@ -17,23 +17,29 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
|
||||||
export const LeftMenus = () => {
|
export const LeftMenus = ({
|
||||||
|
canGoBack,
|
||||||
|
onLeftButtonPress
|
||||||
|
}: {
|
||||||
|
canGoBack?: boolean;
|
||||||
|
onLeftButtonPress?: () => void;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const canGoBack = useNavigationStore((state) => state.canGoBack);
|
|
||||||
const isTablet = deviceMode === "tablet";
|
const isTablet = deviceMode === "tablet";
|
||||||
|
|
||||||
const onLeftButtonPress = () => {
|
const _onLeftButtonPress = () => {
|
||||||
|
if (onLeftButtonPress) return onLeftButtonPress();
|
||||||
|
|
||||||
if (!canGoBack) {
|
if (!canGoBack) {
|
||||||
if (tabBarRef.current?.isDrawerOpen()) {
|
if (tabBarRef.current?.isDrawerOpen()) {
|
||||||
Navigation.closeDrawer();
|
Navigation.closeDrawer();
|
||||||
@@ -60,7 +66,7 @@ export const LeftMenus = () => {
|
|||||||
left={40}
|
left={40}
|
||||||
top={40}
|
top={40}
|
||||||
right={DDS.isLargeTablet() ? 10 : 10}
|
right={DDS.isLargeTablet() ? 10 : 10}
|
||||||
onPress={onLeftButtonPress}
|
onPress={_onLeftButtonPress}
|
||||||
onLongPress={() => {
|
onLongPress={() => {
|
||||||
Navigation.popToTop();
|
Navigation.popToTop();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -20,40 +20,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { Platform, StyleSheet, View } from "react-native";
|
import { Platform, StyleSheet, View } from "react-native";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import Menu from "react-native-reanimated-material-menu";
|
import Menu from "react-native-reanimated-material-menu";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
import SearchService from "../../services/search";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import {
|
||||||
|
HeaderRightButton,
|
||||||
|
RouteName
|
||||||
|
} from "../../stores/use-navigation-store";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
|
||||||
export const RightMenus = () => {
|
export const RightMenus = ({
|
||||||
|
headerRightButtons,
|
||||||
|
renderedInRoute,
|
||||||
|
id,
|
||||||
|
onPressDefaultRightButton,
|
||||||
|
search,
|
||||||
|
onSearch
|
||||||
|
}: {
|
||||||
|
headerRightButtons?: HeaderRightButton[];
|
||||||
|
renderedInRoute: RouteName;
|
||||||
|
id?: string;
|
||||||
|
onPressDefaultRightButton?: () => void;
|
||||||
|
search?: boolean;
|
||||||
|
onSearch?: () => void;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
||||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const buttons = useNavigationStore((state) => state.headerRightButtons);
|
|
||||||
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
|
|
||||||
const buttonAction = useNavigationStore((state) => state.buttonAction);
|
|
||||||
const menuRef = useRef<Menu>(null);
|
const menuRef = useRef<Menu>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.rightBtnContainer}>
|
<View style={styles.rightBtnContainer}>
|
||||||
{!currentScreen.startsWith("Settings") ? (
|
{search ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={async () => {
|
onPress={onSearch}
|
||||||
SearchService.prepareSearch();
|
|
||||||
Navigation.navigate(
|
|
||||||
{
|
|
||||||
name: "Search"
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
testID="icon-search"
|
testID="icon-search"
|
||||||
name="magnify"
|
name="magnify"
|
||||||
color={colors.primary.paragraph}
|
color={colors.primary.paragraph}
|
||||||
@@ -63,9 +69,9 @@ export const RightMenus = () => {
|
|||||||
|
|
||||||
{deviceMode !== "mobile" ? (
|
{deviceMode !== "mobile" ? (
|
||||||
<Button
|
<Button
|
||||||
onPress={buttonAction}
|
onPress={onPressDefaultRightButton}
|
||||||
testID={notesnook.ids.default.addBtn}
|
testID={notesnook.ids.default.addBtn}
|
||||||
icon={currentScreen === "Trash" ? "delete" : "plus"}
|
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
||||||
iconSize={SIZE.xl}
|
iconSize={SIZE.xl}
|
||||||
type="shade"
|
type="shade"
|
||||||
hitSlop={{
|
hitSlop={{
|
||||||
@@ -86,7 +92,7 @@ export const RightMenus = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{buttons && buttons.length > 0 ? (
|
{headerRightButtons && headerRightButtons.length > 0 ? (
|
||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
animationDuration={200}
|
animationDuration={200}
|
||||||
@@ -108,7 +114,7 @@ export const RightMenus = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{buttons.map((item) => (
|
{headerRightButtons.map((item) => (
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
width: 150,
|
width: 150,
|
||||||
|
|||||||
@@ -20,105 +20,74 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import NotebookScreen from "../../screens/notebook";
|
|
||||||
import {
|
import {
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
|
||||||
import { eScrollEvent } from "../../utils/events";
|
import { eScrollEvent } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import Tag from "../ui/tag";
|
import Tag from "../ui/tag";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
|
|
||||||
const titleState: { [id: string]: boolean } = {};
|
export const Title = ({
|
||||||
|
title,
|
||||||
export const Title = () => {
|
isHiddenOnRender,
|
||||||
|
accentColor,
|
||||||
|
isBeta,
|
||||||
|
renderedInRoute,
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
isHiddenOnRender?: boolean;
|
||||||
|
accentColor?: string;
|
||||||
|
isBeta?: boolean;
|
||||||
|
renderedInRoute: string;
|
||||||
|
id?: string;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
const [visible, setVisible] = useState(isHiddenOnRender);
|
||||||
const isNotebook = currentScreen.name === "Notebook";
|
const isTag = title.startsWith("#");
|
||||||
const isTopic = currentScreen?.name === "TopicNotes";
|
|
||||||
const [hide, setHide] = useState(
|
|
||||||
isNotebook
|
|
||||||
? typeof titleState[currentScreen.id as string] === "boolean"
|
|
||||||
? titleState[currentScreen.id as string]
|
|
||||||
: true
|
|
||||||
: false
|
|
||||||
);
|
|
||||||
const isHidden = titleState[currentScreen.id as string];
|
|
||||||
const notebook =
|
|
||||||
isTopic && currentScreen.notebookId
|
|
||||||
? db.notebooks?.notebook(currentScreen.notebookId)?.data
|
|
||||||
: null;
|
|
||||||
const title = currentScreen.title;
|
|
||||||
const isTag = currentScreen?.name === "TaggedNotes";
|
|
||||||
|
|
||||||
const onScroll = useCallback(
|
const onScroll = useCallback(
|
||||||
(data: { x: number; y: number }) => {
|
(data: { x: number; y: number; id?: string; route: string }) => {
|
||||||
if (currentScreen.name !== "Notebook") {
|
if (data.route !== renderedInRoute || data.id !== id) return;
|
||||||
setHide(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.y > 150) {
|
if (data.y > 150) {
|
||||||
if (!hide) return;
|
if (!visible) return;
|
||||||
titleState[currentScreen.id as string] = false;
|
setVisible(false);
|
||||||
setHide(false);
|
|
||||||
} else {
|
} else {
|
||||||
if (hide) return;
|
if (visible) return;
|
||||||
titleState[currentScreen.id as string] = true;
|
setVisible(true);
|
||||||
setHide(true);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentScreen.id, currentScreen.name, hide]
|
[id, renderedInRoute, visible]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentScreen.name === "Notebook") {
|
|
||||||
const value =
|
|
||||||
typeof titleState[currentScreen.id] === "boolean"
|
|
||||||
? titleState[currentScreen.id]
|
|
||||||
: true;
|
|
||||||
setHide(value);
|
|
||||||
} else {
|
|
||||||
setHide(titleState[currentScreen.id as string]);
|
|
||||||
}
|
|
||||||
}, [currentScreen.id, currentScreen.name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent(eScrollEvent, onScroll);
|
eSubscribeEvent(eScrollEvent, onScroll);
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eScrollEvent, onScroll);
|
eUnSubscribeEvent(eScrollEvent, onScroll);
|
||||||
};
|
};
|
||||||
}, [hide, onScroll]);
|
}, [visible, onScroll]);
|
||||||
|
|
||||||
function navigateToNotebook() {
|
|
||||||
if (!isTopic) return;
|
|
||||||
if (notebook) {
|
|
||||||
NotebookScreen.navigate(notebook, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!hide && !isHidden ? (
|
{!visible ? (
|
||||||
<Heading
|
<Heading
|
||||||
onPress={navigateToNotebook}
|
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
marginTop: Platform.OS === "ios" ? -1 : 0
|
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||||
}}
|
}}
|
||||||
color={currentScreen.color || colors.primary.heading}
|
color={accentColor || colors.primary.heading}
|
||||||
>
|
>
|
||||||
{isTag ? (
|
{isTag ? (
|
||||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
||||||
#
|
#
|
||||||
</Heading>
|
</Heading>
|
||||||
) : null}
|
) : null}
|
||||||
{title}{" "}
|
{isTag ? title.slice(1) : title}{" "}
|
||||||
<Tag
|
<Tag
|
||||||
visible={currentScreen.beta}
|
visible={isBeta}
|
||||||
text="BETA"
|
text="BETA"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const SectionHeader = React.memo<
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
console.log("Opening Sort sheet", screen, dataType);
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: <Sort screen={screen} type={dataType} />
|
component: <Sort screen={screen} type={dataType} />
|
||||||
});
|
});
|
||||||
@@ -169,7 +170,7 @@ export const SectionHeader = React.memo<
|
|||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
[dataType !== "notebook"
|
[dataType !== "notebook"
|
||||||
? "notesListMode"
|
? "notesListMode"
|
||||||
: "notebooksListMode"]: isCompactModeEnabled
|
: "notebooksListMode"]: !isCompactModeEnabled
|
||||||
? "compact"
|
? "compact"
|
||||||
: "normal"
|
: "normal"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const NoteItem = ({
|
|||||||
{notebooks?.items
|
{notebooks?.items
|
||||||
?.filter(
|
?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.id !== useNavigationStore.getState().currentScreen?.id
|
item.id !== useNavigationStore.getState().currentRoute?.id
|
||||||
)
|
)
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -49,98 +49,96 @@ type EmptyListProps = {
|
|||||||
screen?: string;
|
screen?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Empty = React.memo(
|
export const Empty = React.memo(function Empty({
|
||||||
function Empty({
|
loading = true,
|
||||||
loading = true,
|
placeholder,
|
||||||
placeholder,
|
title,
|
||||||
title,
|
color,
|
||||||
color,
|
dataType,
|
||||||
dataType,
|
screen
|
||||||
screen
|
}: EmptyListProps) {
|
||||||
}: EmptyListProps) {
|
const { colors } = useThemeColors();
|
||||||
const { colors } = useThemeColors();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const { height } = useWindowDimensions();
|
||||||
const { height } = useWindowDimensions();
|
const introCompleted = useSettingStore(
|
||||||
const introCompleted = useSettingStore(
|
(state) => state.settings.introCompleted
|
||||||
(state) => state.settings.introCompleted
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const tip = useTip(
|
const tip = useTip(
|
||||||
screen === "Notes" && introCompleted
|
screen === "Notes" && introCompleted
|
||||||
? "first-note"
|
? "first-note"
|
||||||
: placeholder?.type || ((dataType + "s") as any),
|
: placeholder?.type || ((dataType + "s") as any),
|
||||||
screen === "Notes" ? "notes" : "list"
|
screen === "Notes" ? "notes" : "list"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
height: height - (140 + insets.top),
|
height: height - (140 + insets.top),
|
||||||
width: "80%",
|
width: "80%",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignSelf: "center"
|
alignSelf: "center"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<>
|
<>
|
||||||
<Tip
|
<Tip
|
||||||
color={color ? color : "accent"}
|
color={color}
|
||||||
tip={tip || ({ text: placeholder?.paragraph } as TTip)}
|
tip={
|
||||||
|
screen !== "Search"
|
||||||
|
? tip || ({ text: placeholder?.paragraph } as TTip)
|
||||||
|
: ({ text: placeholder?.paragraph } as TTip)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
paddingHorizontal: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{placeholder?.button && (
|
||||||
|
<Button
|
||||||
|
testID={notesnook.buttons.add}
|
||||||
|
type="grayAccent"
|
||||||
|
title={placeholder?.button}
|
||||||
|
iconPosition="right"
|
||||||
|
icon="arrow-right"
|
||||||
|
onPress={placeholder?.action}
|
||||||
|
buttonType={{
|
||||||
|
text: color || colors.primary.accent
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "transparent",
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 0
|
borderRadius: 5,
|
||||||
|
height: 40
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{placeholder?.button && (
|
)}
|
||||||
<Button
|
</>
|
||||||
testID={notesnook.buttons.add}
|
) : (
|
||||||
type="grayAccent"
|
<>
|
||||||
title={placeholder?.button}
|
<View
|
||||||
iconPosition="right"
|
style={{
|
||||||
icon="arrow-right"
|
alignSelf: "center",
|
||||||
onPress={placeholder?.action}
|
alignItems: "flex-start",
|
||||||
buttonType={{
|
width: "100%"
|
||||||
text: color || colors.primary.accent
|
}}
|
||||||
}}
|
>
|
||||||
style={{
|
<Heading>{placeholder?.title}</Heading>
|
||||||
alignSelf: "flex-start",
|
<Paragraph size={SIZE.sm} textBreakStrategy="balanced">
|
||||||
borderRadius: 5,
|
{placeholder?.loading}
|
||||||
height: 40
|
</Paragraph>
|
||||||
}}
|
<Seperator />
|
||||||
/>
|
<ActivityIndicator
|
||||||
)}
|
size={SIZE.lg}
|
||||||
</>
|
color={color || colors.primary.accent}
|
||||||
) : (
|
/>
|
||||||
<>
|
</View>
|
||||||
<View
|
</>
|
||||||
style={{
|
)}
|
||||||
alignSelf: "center",
|
</View>
|
||||||
alignItems: "flex-start",
|
);
|
||||||
width: "100%"
|
});
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading>{placeholder?.title}</Heading>
|
|
||||||
<Paragraph size={SIZE.sm} textBreakStrategy="balanced">
|
|
||||||
{placeholder?.loading}
|
|
||||||
</Paragraph>
|
|
||||||
<Seperator />
|
|
||||||
<ActivityIndicator
|
|
||||||
size={SIZE.lg}
|
|
||||||
color={color || colors.primary.accent}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(prev, next) => {
|
|
||||||
if (prev.loading === next.loading) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a tips manager.
|
* Make a tips manager.
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ export default function List(props: ListProps) {
|
|||||||
dataType={props.dataType}
|
dataType={props.dataType}
|
||||||
color={props.customAccentColor}
|
color={props.customAccentColor}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
|
screen={props.renderedInRoute}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ async function resolveNotes(ids: string[]) {
|
|||||||
group.notebooks.map((id) => resolved.notebooks[id])
|
group.notebooks.map((id) => resolved.notebooks[id])
|
||||||
),
|
),
|
||||||
attachmentsCount:
|
attachmentsCount:
|
||||||
(await db.attachments?.ofNote(noteId, "all"))?.length || 0
|
(await db.attachments?.ofNote(noteId, "all").ids())?.length || 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -17,39 +17,28 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { ScrollView, View } from "react-native";
|
import { View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import NotebookScreen from "../../screens/notebook";
|
import NotebookScreen from "../../screens/notebook";
|
||||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
import {
|
import { eClearEditor } from "../../utils/events";
|
||||||
eSendEvent,
|
|
||||||
presentSheet,
|
|
||||||
ToastManager
|
|
||||||
} from "../../services/event-manager";
|
|
||||||
import Navigation from "../../services/navigation";
|
|
||||||
import { useNotebookStore } from "../../stores/use-notebook-store";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import { eClearEditor } from "../../utils/events";
|
|
||||||
|
|
||||||
export default function Notebooks({ note, close, full }) {
|
export default function Notebooks({ note, close, full }) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notebooks = useNotebookStore((state) => state.notebooks);
|
|
||||||
async function getNotebooks(item) {
|
async function getNotebooks(item) {
|
||||||
let filteredNotebooks = [];
|
let filteredNotebooks = [];
|
||||||
const relations = await db.relations.to(note, "notebook").resolve();
|
const relations = await db.relations.to(note, "notebook").resolve();
|
||||||
|
|
||||||
filteredNotebooks.push(relations);
|
filteredNotebooks.push(relations);
|
||||||
|
|
||||||
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
||||||
return filteredNotebooks;
|
return filteredNotebooks;
|
||||||
}
|
}
|
||||||
const [noteNotebooks, setNoteNotebooks] = useState([]);
|
const [noteNotebooks, setNoteNotebooks] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
|
getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
|
||||||
});
|
});
|
||||||
@@ -60,11 +49,6 @@ export default function Notebooks({ note, close, full }) {
|
|||||||
NotebookScreen.navigate(item, true);
|
NotebookScreen.navigate(item, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateTopic = (id, notebookId) => {
|
|
||||||
let item = db.notebooks.notebook(notebookId)?.topics?.topic(id)?._topic;
|
|
||||||
if (!item) return;
|
|
||||||
TopicNotes.navigate(item, true);
|
|
||||||
};
|
|
||||||
const renderItem = (item) => (
|
const renderItem = (item) => (
|
||||||
<View
|
<View
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -105,56 +89,6 @@ export default function Notebooks({ note, close, full }) {
|
|||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
horizontal={true}
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
marginLeft: 8,
|
|
||||||
borderLeftColor: colors.primary.hover,
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
paddingLeft: 8
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.topics.map((topic) => (
|
|
||||||
<Button
|
|
||||||
key={topic.id}
|
|
||||||
onPress={() => {
|
|
||||||
navigateTopic(topic.id, item.id);
|
|
||||||
eSendEvent(eClearEditor);
|
|
||||||
close();
|
|
||||||
}}
|
|
||||||
onLongPress={async () => {
|
|
||||||
await db.notes.removeFromNotebook(
|
|
||||||
{
|
|
||||||
id: item.id,
|
|
||||||
topic: topic.id
|
|
||||||
},
|
|
||||||
note
|
|
||||||
);
|
|
||||||
useNotebookStore.getState().setNotebooks();
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Note removed from topic",
|
|
||||||
context: "local",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
title={topic.title}
|
|
||||||
type="gray"
|
|
||||||
height={30}
|
|
||||||
fontSize={SIZE.xs}
|
|
||||||
icon="bookmark-outline"
|
|
||||||
style={{
|
|
||||||
marginRight: 5,
|
|
||||||
borderRadius: 100,
|
|
||||||
paddingHorizontal: 8
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<View style={{ width: 10 }} />
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
);
|
);
|
||||||
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
|
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
|
||||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
const currentRoute = useNavigationStore((state) => state.currentRoute);
|
||||||
const screen = currentScreen.name;
|
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
SearchService.prepareSearch?.();
|
SearchService.prepareSearch?.();
|
||||||
const allItems = SearchService.getSearchInformation()?.get() || [];
|
const allItems = SearchService.getSearchInformation()?.get() || [];
|
||||||
@@ -205,9 +204,9 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
size={SIZE.xl}
|
size={SIZE.xl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{screen === "Trash" ||
|
{currentRoute === "Trash" ||
|
||||||
screen === "Notebooks" ||
|
currentRoute === "Notebooks" ||
|
||||||
screen === "Reminders" ? null : (
|
currentRoute === "Reminders" ? null : (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
@@ -256,17 +255,15 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{screen === "TopicNotes" || screen === "Notebook" ? (
|
{currentRoute === "Notebook" ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (selectedItemsList.length > 0) {
|
if (selectedItemsList.length > 0) {
|
||||||
const currentScreen =
|
const { focusedRouteId } = useNavigationStore.getState();
|
||||||
useNavigationStore.getState().currentScreen;
|
if (currentRoute === "Notebook") {
|
||||||
|
|
||||||
if (screen === "Notebook") {
|
|
||||||
for (const item of selectedItemsList) {
|
for (const item of selectedItemsList) {
|
||||||
await db.relations.unlink(
|
await db.relations.unlink(
|
||||||
{ type: "notebook", id: currentScreen.id },
|
{ type: "notebook", id: focusedRouteId },
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -287,9 +284,7 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
customStyle={{
|
customStyle={{
|
||||||
marginLeft: 10
|
marginLeft: 10
|
||||||
}}
|
}}
|
||||||
tooltipText={`Remove from ${
|
tooltipText={`Remove from Notebook`}
|
||||||
screen === "Notebook" ? "notebook" : "topic"
|
|
||||||
}`}
|
|
||||||
tooltipPosition={4}
|
tooltipPosition={4}
|
||||||
testID="select-minus"
|
testID="select-minus"
|
||||||
color={colors.primary.paragraph}
|
color={colors.primary.paragraph}
|
||||||
@@ -298,7 +293,7 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{screen === "Favorites" ? (
|
{currentRoute === "Favorites" ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={addToFavorite}
|
onPress={addToFavorite}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
@@ -312,7 +307,7 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{screen === "Trash" ? null : (
|
{currentRoute === "Trash" ? null : (
|
||||||
<IconButton
|
<IconButton
|
||||||
customStyle={{
|
customStyle={{
|
||||||
marginLeft: 10
|
marginLeft: 10
|
||||||
@@ -328,7 +323,7 @@ export const SelectionHeader = React.memo(() => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{screen === "Trash" ? (
|
{currentRoute === "Trash" ? (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
customStyle={{
|
customStyle={{
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import Seperator from "../../ui/seperator";
|
|||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import { MoveNotes } from "../move-notes/movenote";
|
import { MoveNotes } from "../move-notes/movenote";
|
||||||
import { eOnNotebookUpdated } from "../../../utils/events";
|
import { eOnNotebookUpdated } from "../../../utils/events";
|
||||||
|
import { getParentNotebookId } from "../../../utils/notebooks";
|
||||||
|
|
||||||
export const AddNotebookSheet = ({
|
export const AddNotebookSheet = ({
|
||||||
notebook,
|
notebook,
|
||||||
@@ -84,9 +85,12 @@ export const AddNotebookSheet = ({
|
|||||||
useMenuStore.getState().setMenuPins();
|
useMenuStore.getState().setMenuPins();
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
eSendEvent(eOnNotebookUpdated, parentNotebook?.id);
|
|
||||||
if (notebook) {
|
if (notebook) {
|
||||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
const parent = await getParentNotebookId(notebook.id);
|
||||||
|
eSendEvent(eOnNotebookUpdated, parent);
|
||||||
|
setImmediate(() => {
|
||||||
|
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notebook) {
|
if (!notebook) {
|
||||||
@@ -136,6 +140,11 @@ export const AddNotebookSheet = ({
|
|||||||
onChangeText={(value) => {
|
onChangeText={(value) => {
|
||||||
title.current = value;
|
title.current = value;
|
||||||
}}
|
}}
|
||||||
|
onLayout={() => {
|
||||||
|
setImmediate(() => {
|
||||||
|
titleInput?.current?.focus();
|
||||||
|
});
|
||||||
|
}}
|
||||||
placeholder="Enter a title"
|
placeholder="Enter a title"
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
descriptionInput.current?.focus();
|
descriptionInput.current?.focus();
|
||||||
@@ -160,8 +169,13 @@ export const AddNotebookSheet = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddNotebookSheet.present = (notebook?: Notebook, parentNotebook?: Notebook) => {
|
AddNotebookSheet.present = (
|
||||||
|
notebook?: Notebook,
|
||||||
|
parentNotebook?: Notebook,
|
||||||
|
context?: string
|
||||||
|
) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
|
context: context,
|
||||||
component: (ref, close) => (
|
component: (ref, close) => (
|
||||||
<AddNotebookSheet
|
<AddNotebookSheet
|
||||||
notebook={notebook}
|
notebook={notebook}
|
||||||
|
|||||||
@@ -1,29 +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 { createContext, useContext } from "react";
|
|
||||||
|
|
||||||
export const SelectionContext = createContext({
|
|
||||||
toggleSelection: (item) => {},
|
|
||||||
deselect: (item) => {},
|
|
||||||
select: (item) => {},
|
|
||||||
deselectAll: () => {}
|
|
||||||
});
|
|
||||||
export const SelectionProvider = SelectionContext.Provider;
|
|
||||||
export const useSelectionContext = () => useContext(SelectionContext);
|
|
||||||
@@ -1,76 +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 React, { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import { FlashList } from "react-native-actions-sheet";
|
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import { ListHeaderInputItem } from "./list-header-item.js";
|
|
||||||
|
|
||||||
export const FilteredList = ({
|
|
||||||
data,
|
|
||||||
itemType,
|
|
||||||
onAddItem,
|
|
||||||
hasHeaderSearch,
|
|
||||||
listRef,
|
|
||||||
...restProps
|
|
||||||
}) => {
|
|
||||||
const [filtered, setFiltered] = useState(data);
|
|
||||||
const query = useRef();
|
|
||||||
const onChangeText = useCallback(
|
|
||||||
(value) => {
|
|
||||||
query.current = value;
|
|
||||||
try {
|
|
||||||
if (!value) return setFiltered(data);
|
|
||||||
const results = db.lookup[itemType + "s"]([...data], value);
|
|
||||||
setFiltered(results);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[data, itemType]
|
|
||||||
);
|
|
||||||
const onSubmit = async (value) => {
|
|
||||||
return await onAddItem(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onChangeText(query.current);
|
|
||||||
}, [data, onChangeText]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FlashList
|
|
||||||
{...restProps}
|
|
||||||
data={filtered}
|
|
||||||
ref={listRef}
|
|
||||||
ListHeaderComponent={
|
|
||||||
hasHeaderSearch ? (
|
|
||||||
<ListHeaderInputItem
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
itemType={itemType}
|
|
||||||
testID={"list-input" + itemType}
|
|
||||||
placeholder={`Search or add a new ${itemType}`}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
keyboardShouldPersistTaps="always"
|
|
||||||
keyboardDismissMode="none"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -17,12 +17,11 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Note, Notebook } from "@notesnook/core";
|
import { GroupHeader, Note } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { RefObject, useCallback, useEffect, useMemo } from "react";
|
import React, { RefObject, useCallback, useEffect } from "react";
|
||||||
import { Keyboard, TouchableOpacity, View } from "react-native";
|
import { Keyboard, TouchableOpacity, View } from "react-native";
|
||||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
import { ActionSheetRef, FlashList } from "react-native-actions-sheet";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
@@ -36,24 +35,53 @@ import { Dialog } from "../../dialog";
|
|||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { SelectionProvider } from "./context";
|
import { NotebookItem } from "./notebook-item";
|
||||||
import { FilteredList } from "./filtered-list";
|
import { useNotebookItemSelectionStore } from "./store";
|
||||||
import { ListItem } from "./list-item";
|
import SheetProvider from "../../sheet-provider";
|
||||||
import { useItemSelectionStore } from "./store";
|
import { ItemSelection } from "../../../stores/item-selection-store";
|
||||||
|
|
||||||
|
async function updateInitialSelectionState(items: string[]) {
|
||||||
|
const relations = await db.relations
|
||||||
|
.to(
|
||||||
|
{
|
||||||
|
type: "note",
|
||||||
|
ids: items
|
||||||
|
},
|
||||||
|
"notebook"
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
const initialSelectionState: ItemSelection = {};
|
||||||
|
const notebookIds = [
|
||||||
|
...new Set(relations.map((relation) => relation.fromId))
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const id of notebookIds) {
|
||||||
|
const all = items.every((noteId) => {
|
||||||
|
return (
|
||||||
|
relations.findIndex(
|
||||||
|
(relation) => relation.fromId === id && relation.toId === noteId
|
||||||
|
) > -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (all) {
|
||||||
|
initialSelectionState[id] = "selected";
|
||||||
|
} else {
|
||||||
|
initialSelectionState[id] = "intermediate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useNotebookItemSelectionStore.setState({
|
||||||
|
initialState: initialSelectionState,
|
||||||
|
selection: { ...initialSelectionState },
|
||||||
|
multiSelect: relations.length > 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Render all notebooks
|
|
||||||
* Render sub notebooks
|
|
||||||
* fix selection, remove topics stuff.
|
|
||||||
* show already selected notebooks regardless of their level
|
|
||||||
* show intermediate selection for nested notebooks at all levels.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const MoveNoteSheet = ({
|
const MoveNoteSheet = ({
|
||||||
note,
|
note,
|
||||||
actionSheetRef
|
actionSheetRef
|
||||||
}: {
|
}: {
|
||||||
note: Note;
|
note: Note | undefined;
|
||||||
actionSheetRef: RefObject<ActionSheetRef>;
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -63,72 +91,42 @@ const MoveNoteSheet = ({
|
|||||||
(state) => state.selectedItemsList
|
(state) => state.selectedItemsList
|
||||||
);
|
);
|
||||||
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||||
|
const multiSelect = useNotebookItemSelectionStore(
|
||||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
(state) => state.multiSelect
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const items = note
|
||||||
|
? [note.id]
|
||||||
|
: (selectedItemsList as Note[]).map((note) => note.id);
|
||||||
|
updateInitialSelectionState(items);
|
||||||
return () => {
|
return () => {
|
||||||
useItemSelectionStore.getState().setMultiSelect(false);
|
useNotebookItemSelectionStore.setState({
|
||||||
useItemSelectionStore.getState().setItemState({});
|
initialState: {},
|
||||||
|
selection: {},
|
||||||
|
multiSelect: false,
|
||||||
|
canEnableMultiSelectMode: true
|
||||||
|
});
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [note, selectedItemsList]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateItemState = useCallback(function (
|
|
||||||
item: Notebook,
|
|
||||||
state: "selected" | "intermediate" | "deselected"
|
|
||||||
) {
|
|
||||||
const itemState = { ...useItemSelectionStore.getState().itemState };
|
|
||||||
const mergeState = {
|
|
||||||
[item.id]: state
|
|
||||||
};
|
|
||||||
useItemSelectionStore.getState().setItemState({
|
|
||||||
...itemState,
|
|
||||||
...mergeState
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[]);
|
|
||||||
|
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
|
||||||
toggleSelection: (item: Notebook) => {
|
|
||||||
const itemState = useItemSelectionStore.getState().itemState;
|
|
||||||
if (itemState[item.id] === "selected") {
|
|
||||||
updateItemState(item, "deselected");
|
|
||||||
} else {
|
|
||||||
updateItemState(item, "selected");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deselect: (item: Notebook) => {
|
|
||||||
updateItemState(item, "deselected");
|
|
||||||
},
|
|
||||||
select: (item: Notebook) => {
|
|
||||||
updateItemState(item, "selected");
|
|
||||||
},
|
|
||||||
deselectAll: () => {
|
|
||||||
useItemSelectionStore.setState({
|
|
||||||
itemState: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
[updateItemState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSave = async () => {
|
const onSave = async () => {
|
||||||
const noteIds = note
|
const noteIds = note
|
||||||
? [note.id]
|
? [note.id]
|
||||||
: selectedItemsList.map((n) => (n as Note).id);
|
: selectedItemsList.map((n) => (n as Note).id);
|
||||||
const itemState = useItemSelectionStore.getState().itemState;
|
|
||||||
for (const id in itemState) {
|
const changedNotebooks = useNotebookItemSelectionStore.getState().selection;
|
||||||
|
|
||||||
|
for (const id in changedNotebooks) {
|
||||||
const item = await db.notebooks.notebook(id);
|
const item = await db.notebooks.notebook(id);
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
if (itemState[id] === "selected") {
|
if (changedNotebooks[id] === "selected") {
|
||||||
for (let noteId of noteIds) {
|
for (const id of noteIds) {
|
||||||
await db.relations.add(item, { id: noteId, type: "note" });
|
await db.relations.add(item, { id: id, type: "note" });
|
||||||
}
|
}
|
||||||
} else if (itemState[id] === "deselected") {
|
} else if (changedNotebooks[id] === "deselected") {
|
||||||
for (let noteId of noteIds) {
|
for (const id of noteIds) {
|
||||||
await db.relations.unlink(item, { id: noteId, type: "note" });
|
await db.relations.unlink(item, { id: id, type: "note" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,9 +139,18 @@ const MoveNoteSheet = ({
|
|||||||
actionSheetRef.current?.hide();
|
actionSheetRef.current?.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderNotebook = useCallback(
|
||||||
|
({ item, index }: { item: string | GroupHeader; index: number }) =>
|
||||||
|
(item as GroupHeader).type === "header" ? null : (
|
||||||
|
<NotebookItem items={notebooks} id={item as string} index={index} />
|
||||||
|
),
|
||||||
|
[notebooks]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog context="move_note" />
|
<Dialog context="move_note" />
|
||||||
|
<SheetProvider context="link-notebooks" />
|
||||||
<View>
|
<View>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
@@ -204,127 +211,50 @@ const MoveNoteSheet = ({
|
|||||||
}}
|
}}
|
||||||
type="grayAccent"
|
type="grayAccent"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
useItemSelectionStore.setState({
|
const items = note
|
||||||
itemState: {}
|
? [note.id]
|
||||||
});
|
: (selectedItemsList as Note[]).map((note) => note.id);
|
||||||
|
updateInitialSelectionState(items);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<SelectionProvider value={contextValue}>
|
<View
|
||||||
<View
|
style={{
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
maxHeight: dimensions.height * 0.85,
|
||||||
|
height: 50 * ((notebooks?.ids.length || 0) + 2)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FlashList
|
||||||
|
data={notebooks?.ids?.filter((id) => typeof id === "string")}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
width: "100%"
|
||||||
maxHeight: dimensions.height * 0.85,
|
|
||||||
height: 50 * ((notebooks?.ids.length || 0) + 2)
|
|
||||||
}}
|
}}
|
||||||
>
|
estimatedItemSize={50}
|
||||||
<FilteredList
|
keyExtractor={(item) => item as string}
|
||||||
ListEmptyComponent={
|
renderItem={renderNotebook}
|
||||||
notebooks?.ids.length ? null : (
|
ListEmptyComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
flex: 1,
|
||||||
height: "100%",
|
justifyContent: "center",
|
||||||
justifyContent: "center",
|
alignItems: "center",
|
||||||
alignItems: "center"
|
height: 200
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Paragraph color={colors.primary.icon}>No notebooks</Paragraph>
|
||||||
name="book-outline"
|
</View>
|
||||||
color={colors.primary.icon}
|
}
|
||||||
size={100}
|
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||||
/>
|
/>
|
||||||
<Paragraph style={{ marginBottom: 10 }}>
|
</View>
|
||||||
You do not have any notebooks.
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
estimatedItemSize={50}
|
|
||||||
data={notebooks?.ids.length}
|
|
||||||
hasHeaderSearch={true}
|
|
||||||
renderItem={({ item, index }) => (
|
|
||||||
<ListItem
|
|
||||||
item={item}
|
|
||||||
key={item.id}
|
|
||||||
index={index}
|
|
||||||
hasNotes={getSelectedNotesCountInItem(item) > 0}
|
|
||||||
sheetRef={actionSheetRef}
|
|
||||||
infoText={
|
|
||||||
<>
|
|
||||||
{item.topics.length === 1
|
|
||||||
? item.topics.length + " topic"
|
|
||||||
: item.topics.length + " topics"}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
getListItems={getItemsForItem}
|
|
||||||
getSublistItemProps={(topic) => ({
|
|
||||||
hasNotes: getSelectedNotesCountInItem(topic) > 0,
|
|
||||||
style: {
|
|
||||||
marginBottom: 0,
|
|
||||||
height: 40
|
|
||||||
},
|
|
||||||
onPress: (item) => {
|
|
||||||
const itemState =
|
|
||||||
useItemSelectionStore.getState().itemState;
|
|
||||||
const currentState = itemState[item.id];
|
|
||||||
if (currentState !== "selected") {
|
|
||||||
resetItemState("deselected");
|
|
||||||
contextValue.select(item);
|
|
||||||
} else {
|
|
||||||
contextValue.deselect(item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key: item.id,
|
|
||||||
type: "transparent"
|
|
||||||
})}
|
|
||||||
icon={(expanded) => ({
|
|
||||||
name: expanded ? "chevron-up" : "chevron-down",
|
|
||||||
color: expanded
|
|
||||||
? colors.primary.accent
|
|
||||||
: colors.primary.paragraph
|
|
||||||
})}
|
|
||||||
onScrollEnd={() => {
|
|
||||||
actionSheetRef.current?.handleChildScrollEnd();
|
|
||||||
}}
|
|
||||||
hasSubList={true}
|
|
||||||
hasHeaderSearch={false}
|
|
||||||
type="grayBg"
|
|
||||||
sublistItemType="topic"
|
|
||||||
onAddItem={(title) => {
|
|
||||||
return onAddTopic(title, item);
|
|
||||||
}}
|
|
||||||
onAddSublistItem={(item) => {
|
|
||||||
openAddTopicDialog(item);
|
|
||||||
}}
|
|
||||||
onPress={(item) => {
|
|
||||||
const itemState =
|
|
||||||
useItemSelectionStore.getState().itemState;
|
|
||||||
const currentState = itemState[item.id];
|
|
||||||
if (currentState !== "selected") {
|
|
||||||
resetItemState("deselected");
|
|
||||||
contextValue.select(item);
|
|
||||||
} else {
|
|
||||||
contextValue.deselect(item);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
itemType="notebook"
|
|
||||||
onAddItem={async (title) => {
|
|
||||||
return await onAddNotebook(title);
|
|
||||||
}}
|
|
||||||
ListFooterComponent={<View style={{ height: 20 }} />}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</SelectionProvider>
|
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MoveNoteSheet.present = (note) => {
|
MoveNoteSheet.present = (note?: Note) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
||||||
enableGesturesInScrollView: false,
|
enableGesturesInScrollView: false,
|
||||||
|
|||||||
@@ -1,88 +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 React, { useRef, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import Input from "../../ui/input";
|
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
|
|
||||||
export const ListHeaderInputItem = ({
|
|
||||||
onSubmit,
|
|
||||||
onChangeText,
|
|
||||||
placeholder,
|
|
||||||
testID
|
|
||||||
}) => {
|
|
||||||
const [focused, setFocused] = useState(false);
|
|
||||||
const { colors } = useThemeColors("sheet");
|
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState();
|
|
||||||
const inputRef = useRef();
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
fwdRef={inputRef}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
setInputValue(value);
|
|
||||||
onChangeText?.(value);
|
|
||||||
}}
|
|
||||||
testID={testID}
|
|
||||||
blurOnSubmit={false}
|
|
||||||
onFocusInput={() => {
|
|
||||||
setFocused(true);
|
|
||||||
}}
|
|
||||||
onBlurInput={() => {
|
|
||||||
setFocused(false);
|
|
||||||
}}
|
|
||||||
button={{
|
|
||||||
icon: inputValue ? "plus" : "magnify",
|
|
||||||
color: focused ? colors.selected.icon : colors.secondary.icon,
|
|
||||||
onPress: async () => {
|
|
||||||
const result = await onSubmit(inputValue);
|
|
||||||
if (result) {
|
|
||||||
inputRef.current?.blur();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder={placeholder}
|
|
||||||
marginBottom={5}
|
|
||||||
/>
|
|
||||||
{inputValue ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: colors.primary.shade,
|
|
||||||
padding: 5,
|
|
||||||
borderRadius: 5,
|
|
||||||
marginBottom: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph color={colors.primary.accent}>
|
|
||||||
Tap on + to add {`"${inputValue}"`}
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,293 +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, { useEffect, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
|
||||||
import { IconButton } from "../../ui/icon-button";
|
|
||||||
import { PressableButton } from "../../ui/pressable";
|
|
||||||
import Heading from "../../ui/typography/heading";
|
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
|
||||||
import { useSelectionContext } from "./context";
|
|
||||||
import { FilteredList } from "./filtered-list";
|
|
||||||
import { useItemSelectionStore } from "./store";
|
|
||||||
|
|
||||||
const SelectionIndicator = ({
|
|
||||||
item,
|
|
||||||
hasNotes,
|
|
||||||
selectItem,
|
|
||||||
onPress,
|
|
||||||
onChange
|
|
||||||
}) => {
|
|
||||||
const itemState = useItemSelectionStore((state) => state.itemState[item.id]);
|
|
||||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
|
||||||
|
|
||||||
const isSelected = itemState === "selected";
|
|
||||||
const isIntermediate = itemState === "intermediate";
|
|
||||||
const isRemoved = !isSelected && hasNotes;
|
|
||||||
const { colors } = useThemeColors("sheet");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onChange?.();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [itemState]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
size={22}
|
|
||||||
customStyle={{
|
|
||||||
marginRight: 5,
|
|
||||||
width: 23,
|
|
||||||
height: 23
|
|
||||||
}}
|
|
||||||
color={
|
|
||||||
isRemoved
|
|
||||||
? colors.static.red
|
|
||||||
: isIntermediate || isSelected
|
|
||||||
? colors.selected.icon
|
|
||||||
: colors.primary.icon
|
|
||||||
}
|
|
||||||
onPress={() => {
|
|
||||||
if (multiSelect) return selectItem();
|
|
||||||
onPress?.(item);
|
|
||||||
}}
|
|
||||||
onLongPress={() => {
|
|
||||||
useItemSelectionStore.getState().setMultiSelect(true);
|
|
||||||
selectItem();
|
|
||||||
}}
|
|
||||||
testID={
|
|
||||||
isRemoved
|
|
||||||
? "close-circle-outline"
|
|
||||||
: isSelected
|
|
||||||
? "check-circle-outline"
|
|
||||||
: isIntermediate
|
|
||||||
? "minus-circle-outline"
|
|
||||||
: "checkbox-blank-circle-outline"
|
|
||||||
}
|
|
||||||
name={
|
|
||||||
isRemoved
|
|
||||||
? "close-circle-outline"
|
|
||||||
: isSelected
|
|
||||||
? "check-circle-outline"
|
|
||||||
: isIntermediate
|
|
||||||
? "minus-circle-outline"
|
|
||||||
: "checkbox-blank-circle-outline"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ListItem = ({
|
|
||||||
item,
|
|
||||||
index,
|
|
||||||
icon,
|
|
||||||
infoText,
|
|
||||||
hasSubList,
|
|
||||||
onPress,
|
|
||||||
onScrollEnd,
|
|
||||||
getListItems,
|
|
||||||
style,
|
|
||||||
type,
|
|
||||||
sublistItemType,
|
|
||||||
onAddItem,
|
|
||||||
getSublistItemProps,
|
|
||||||
hasHeaderSearch,
|
|
||||||
onAddSublistItem,
|
|
||||||
hasNotes,
|
|
||||||
onChange,
|
|
||||||
sheetRef
|
|
||||||
}) => {
|
|
||||||
const { toggleSelection } = useSelectionContext();
|
|
||||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
|
||||||
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
|
|
||||||
const { colors } = useThemeColors("sheet");
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
function selectItem() {
|
|
||||||
toggleSelection(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSelectedNotesCountInNotebookTopics = (item) => {
|
|
||||||
if (item.type === "topic") return;
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
const noteIds = [];
|
|
||||||
for (let topic of item.topics) {
|
|
||||||
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
|
|
||||||
if (useItemSelectionStore.getState().itemState[topic.id] === "selected") {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useSelectionStore.getState().selectedItemsList.forEach((item) => {
|
|
||||||
if (noteIds.indexOf(item.id) > -1) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
|
|
||||||
}, [item]);
|
|
||||||
|
|
||||||
const onChangeSubItem = () => {
|
|
||||||
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
overflow: "hidden",
|
|
||||||
marginBottom: 10,
|
|
||||||
...style
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PressableButton
|
|
||||||
onPress={() => {
|
|
||||||
if (hasSubList) return setExpanded(!expanded);
|
|
||||||
if (multiSelect) return selectItem();
|
|
||||||
onPress?.(item);
|
|
||||||
}}
|
|
||||||
type={type}
|
|
||||||
onLongPress={() => {
|
|
||||||
useItemSelectionStore.getState().setMultiSelect(true);
|
|
||||||
selectItem();
|
|
||||||
}}
|
|
||||||
customStyle={{
|
|
||||||
height: style?.height || 50,
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "flex-start"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: 50,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingHorizontal: 12
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectionIndicator
|
|
||||||
hasNotes={hasNotes}
|
|
||||||
onPress={onPress}
|
|
||||||
item={item}
|
|
||||||
onChange={onChange}
|
|
||||||
selectItem={selectItem}
|
|
||||||
/>
|
|
||||||
<View>
|
|
||||||
{hasSubList && expanded ? (
|
|
||||||
<Heading size={SIZE.md}>{item.title}</Heading>
|
|
||||||
) : (
|
|
||||||
<Paragraph size={SIZE.sm}>{item.title}</Paragraph>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{infoText ? (
|
|
||||||
<Paragraph size={SIZE.xs} color={colors.primary.icon}>
|
|
||||||
{infoText}
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showSelectedIndicator ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: colors.primary.accent,
|
|
||||||
width: 7,
|
|
||||||
height: 7,
|
|
||||||
borderRadius: 100,
|
|
||||||
marginRight: 12
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{onAddSublistItem ? (
|
|
||||||
<IconButton
|
|
||||||
name={"plus"}
|
|
||||||
testID="add-item-icon"
|
|
||||||
color={colors.primary.paragraph}
|
|
||||||
size={SIZE.xl}
|
|
||||||
onPress={() => {
|
|
||||||
onAddSublistItem(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{icon ? (
|
|
||||||
<IconButton
|
|
||||||
name={icon(expanded).name}
|
|
||||||
color={icon(expanded).color}
|
|
||||||
size={icon(expanded).size || SIZE.xl}
|
|
||||||
onPress={
|
|
||||||
hasSubList
|
|
||||||
? () => setExpanded(!expanded)
|
|
||||||
: icon(expanded).onPress
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</PressableButton>
|
|
||||||
|
|
||||||
{expanded && hasSubList ? (
|
|
||||||
<FilteredList
|
|
||||||
nestedScrollEnabled
|
|
||||||
data={getListItems(item)}
|
|
||||||
keyboardShouldPersistTaps="always"
|
|
||||||
keyboardDismissMode="none"
|
|
||||||
onMomentumScrollEnd={onScrollEnd}
|
|
||||||
style={{
|
|
||||||
width: "95%",
|
|
||||||
alignSelf: "flex-end",
|
|
||||||
maxHeight: 250
|
|
||||||
}}
|
|
||||||
estimatedItemSize={40}
|
|
||||||
itemType={sublistItemType}
|
|
||||||
hasHeaderSearch={hasHeaderSearch}
|
|
||||||
renderItem={({ item, index }) => (
|
|
||||||
<ListItem
|
|
||||||
item={item}
|
|
||||||
{...getSublistItemProps(item)}
|
|
||||||
index={index}
|
|
||||||
onChange={onChangeSubItem}
|
|
||||||
onScrollEnd={onScrollEnd}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
onAddItem={onAddItem}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
236
apps/mobile/app/components/sheets/add-to/notebook-item.tsx
Normal file
236
apps/mobile/app/components/sheets/add-to/notebook-item.tsx
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
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, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { View, useWindowDimensions } from "react-native";
|
||||||
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
|
import { useTotalNotes } from "../../../hooks/use-db-item";
|
||||||
|
import { useNotebook } from "../../../hooks/use-notebook";
|
||||||
|
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||||
|
import { SIZE } from "../../../utils/size";
|
||||||
|
import { IconButton } from "../../ui/icon-button";
|
||||||
|
import { PressableButton } from "../../ui/pressable";
|
||||||
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
import { AddNotebookSheet } from "../add-notebook";
|
||||||
|
import {
|
||||||
|
useNotebookExpandedStore,
|
||||||
|
useNotebookItemSelectionStore
|
||||||
|
} from "./store";
|
||||||
|
|
||||||
|
type NotebookParentProp = {
|
||||||
|
parent?: NotebookParentProp;
|
||||||
|
item?: Notebook;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NotebookItem = ({
|
||||||
|
id,
|
||||||
|
currentLevel = 0,
|
||||||
|
index,
|
||||||
|
parent,
|
||||||
|
items
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
currentLevel?: number;
|
||||||
|
index: number;
|
||||||
|
parent?: NotebookParentProp;
|
||||||
|
items?: VirtualizedGrouping<Notebook>;
|
||||||
|
}) => {
|
||||||
|
const { nestedNotebooks, notebook: item } = useNotebook(id, items);
|
||||||
|
const ids = useMemo(() => (id ? [id] : []), [id]);
|
||||||
|
const { totalNotes: totalNotes } = useTotalNotes(ids, "notebook");
|
||||||
|
const screen = useNavigationStore((state) => state.currentRoute);
|
||||||
|
const { colors } = useThemeColors("sheet");
|
||||||
|
const selection = useNotebookItemSelectionStore((state) =>
|
||||||
|
id ? state.selection[id] : undefined
|
||||||
|
);
|
||||||
|
const isSelected = selection === "selected";
|
||||||
|
const isFocused = screen.id === id;
|
||||||
|
const { fontScale } = useWindowDimensions();
|
||||||
|
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||||
|
|
||||||
|
const onPress = () => {
|
||||||
|
if (!item) return;
|
||||||
|
const state = useNotebookItemSelectionStore.getState();
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
state.markAs(item, !state.initialState[id] ? undefined : "deselected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.multiSelect) {
|
||||||
|
const keys = Object.keys(state.selection);
|
||||||
|
const nextState: any = {};
|
||||||
|
for (const key in keys) {
|
||||||
|
nextState[key] = !state.initialState[key] ? undefined : "deselected";
|
||||||
|
}
|
||||||
|
console.log("Single item selection");
|
||||||
|
state.setSelection({
|
||||||
|
[item.id]: "selected",
|
||||||
|
...nextState
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Multi item selection");
|
||||||
|
state.markAs(item, "selected");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingLeft: currentLevel > 0 && currentLevel < 6 ? 15 : undefined,
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PressableButton
|
||||||
|
type={"transparent"}
|
||||||
|
onLongPress={() => {
|
||||||
|
if (!item) return;
|
||||||
|
useNotebookItemSelectionStore.setState({
|
||||||
|
multiSelect: true
|
||||||
|
});
|
||||||
|
useNotebookItemSelectionStore.getState().markAs(item, "selected");
|
||||||
|
}}
|
||||||
|
testID={`add-to-notebook-item-${currentLevel}-${index}`}
|
||||||
|
onPress={onPress}
|
||||||
|
customStyle={{
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "row",
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 12,
|
||||||
|
borderRadius: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
size={SIZE.lg}
|
||||||
|
color={
|
||||||
|
isSelected
|
||||||
|
? colors.selected.icon
|
||||||
|
: selection === "deselected"
|
||||||
|
? colors.error.accent
|
||||||
|
: colors.primary.icon
|
||||||
|
}
|
||||||
|
onPress={onPress}
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
bottom={0}
|
||||||
|
right={0}
|
||||||
|
customStyle={{
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}}
|
||||||
|
name={
|
||||||
|
selection === "deselected"
|
||||||
|
? "close-circle-outline"
|
||||||
|
: isSelected
|
||||||
|
? "check-circle-outline"
|
||||||
|
: selection === "intermediate"
|
||||||
|
? "minus-circle-outline"
|
||||||
|
: "checkbox-blank-circle-outline"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{nestedNotebooks?.ids.length ? (
|
||||||
|
<IconButton
|
||||||
|
size={SIZE.lg}
|
||||||
|
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||||
|
onPress={() => {
|
||||||
|
useNotebookExpandedStore.getState().setExpanded(id);
|
||||||
|
}}
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
bottom={0}
|
||||||
|
right={0}
|
||||||
|
customStyle={{
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}}
|
||||||
|
name={expanded ? "chevron-down" : "chevron-right"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Paragraph
|
||||||
|
color={
|
||||||
|
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
|
||||||
|
}
|
||||||
|
size={SIZE.sm}
|
||||||
|
>
|
||||||
|
{item?.title}{" "}
|
||||||
|
{totalNotes?.(id) ? (
|
||||||
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
|
{totalNotes(id)}
|
||||||
|
</Paragraph>
|
||||||
|
) : null}
|
||||||
|
</Paragraph>
|
||||||
|
</View>
|
||||||
|
<IconButton
|
||||||
|
name="plus"
|
||||||
|
customStyle={{
|
||||||
|
width: 40 * fontScale,
|
||||||
|
height: 40 * fontScale
|
||||||
|
}}
|
||||||
|
testID={notesnook.ids.notebook.menu}
|
||||||
|
onPress={() => {
|
||||||
|
if (!item) return;
|
||||||
|
AddNotebookSheet.present(undefined, item, "link-notebooks");
|
||||||
|
}}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
top={0}
|
||||||
|
color={colors.primary.icon}
|
||||||
|
size={SIZE.xl}
|
||||||
|
/>
|
||||||
|
</PressableButton>
|
||||||
|
|
||||||
|
{!expanded
|
||||||
|
? null
|
||||||
|
: nestedNotebooks?.ids.map((id, index) => (
|
||||||
|
<NotebookItem
|
||||||
|
key={id as string}
|
||||||
|
id={id as string}
|
||||||
|
index={index}
|
||||||
|
currentLevel={currentLevel + 1}
|
||||||
|
items={nestedNotebooks}
|
||||||
|
parent={{
|
||||||
|
parent: parent,
|
||||||
|
item: item
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,27 +16,24 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import create, { State } from "zustand";
|
import create from "zustand";
|
||||||
|
import { createItemSelectionStore } from "../../../stores/item-selection-store";
|
||||||
|
|
||||||
type SelectionItemState = Record<
|
export const useNotebookExpandedStore = create<{
|
||||||
string,
|
expanded: {
|
||||||
"intermediate" | "selected" | "deselected"
|
[id: string]: boolean;
|
||||||
>;
|
};
|
||||||
|
setExpanded: (id: string) => void;
|
||||||
export interface SelectionStore extends State {
|
}>((set, get) => ({
|
||||||
itemState: SelectionItemState;
|
expanded: {},
|
||||||
setItemState: (state: SelectionItemState) => void;
|
setExpanded(id: string) {
|
||||||
multiSelect: boolean;
|
|
||||||
setMultiSelect: (multiSelect: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useItemSelectionStore = create<SelectionStore>((set) => ({
|
|
||||||
itemState: {},
|
|
||||||
setItemState: (itemState) => {
|
|
||||||
set({
|
set({
|
||||||
itemState
|
expanded: {
|
||||||
|
...get().expanded,
|
||||||
|
[id]: !get().expanded[id]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
multiSelect: false,
|
|
||||||
setMultiSelect: (multiSelect) => set({ multiSelect })
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const useNotebookItemSelectionStore = createItemSelectionStore(true);
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { Tags } from "@notesnook/core/dist/collections/tags";
|
import { Tags } from "@notesnook/core/dist/collections/tags";
|
||||||
import { Note, Tag, isGroupHeader } from "@notesnook/core/dist/types";
|
import { Note, Tag } from "@notesnook/core/dist/types";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, {
|
import React, {
|
||||||
RefObject,
|
RefObject,
|
||||||
@@ -28,12 +29,21 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from "react";
|
} from "react";
|
||||||
import { TextInput, View } from "react-native";
|
import { TextInput, View, useWindowDimensions } from "react-native";
|
||||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
import {
|
||||||
|
ActionSheetRef,
|
||||||
|
FlashList,
|
||||||
|
FlatList
|
||||||
|
} from "react-native-actions-sheet";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
|
import { useDBItem } from "../../../hooks/use-db-item";
|
||||||
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
|
import {
|
||||||
|
ItemSelection,
|
||||||
|
createItemSelectionStore
|
||||||
|
} from "../../../stores/item-selection-store";
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
import { useTagStore } from "../../../stores/use-tag-store";
|
import { useTagStore } from "../../../stores/use-tag-store";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
@@ -42,17 +52,40 @@ import Input from "../../ui/input";
|
|||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { VirtualizedGrouping } from "@notesnook/core";
|
|
||||||
|
|
||||||
function tagHasSomeNotes(tagId: string, noteIds: string[]) {
|
async function updateInitialSelectionState(items: string[]) {
|
||||||
return db.relations.from({ type: "tag", id: tagId }, "note").has(...noteIds);
|
const relations = await db.relations
|
||||||
|
.to(
|
||||||
|
{
|
||||||
|
type: "note",
|
||||||
|
ids: items
|
||||||
|
},
|
||||||
|
"tag"
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
const initialSelectionState: ItemSelection = {};
|
||||||
|
const tagId = [...new Set(relations.map((relation) => relation.fromId))];
|
||||||
|
|
||||||
|
for (const id of tagId) {
|
||||||
|
const all = items.every((noteId) => {
|
||||||
|
return (
|
||||||
|
relations.findIndex(
|
||||||
|
(relation) => relation.fromId === id && relation.toId === noteId
|
||||||
|
) > -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (all) {
|
||||||
|
initialSelectionState[id] = "selected";
|
||||||
|
} else {
|
||||||
|
initialSelectionState[id] = "intermediate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialSelectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tagHasAllNotes(tagId: string, noteIds: string[]) {
|
const useTagItemSelection = createItemSelectionStore(true);
|
||||||
return db.relations
|
|
||||||
.from({ type: "tag", id: tagId }, "note")
|
|
||||||
.hasAll(...noteIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ManageTagsSheet = (props: {
|
const ManageTagsSheet = (props: {
|
||||||
notes?: Note[];
|
notes?: Note[];
|
||||||
@@ -60,12 +93,44 @@ const ManageTagsSheet = (props: {
|
|||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notes = useMemo(() => props.notes || [], [props.notes]);
|
const notes = useMemo(() => props.notes || [], [props.notes]);
|
||||||
const tags = useTagStore((state) => state.tags);
|
const [tags, setTags] = useState<VirtualizedGrouping<Tag>>();
|
||||||
|
|
||||||
const [query, setQuery] = useState<string>();
|
const [query, setQuery] = useState<string>();
|
||||||
const inputRef = useRef<TextInput>(null);
|
const inputRef = useRef<TextInput>(null);
|
||||||
const [focus, setFocus] = useState(false);
|
const [focus, setFocus] = useState(false);
|
||||||
const [queryExists, setQueryExists] = useState(false);
|
const [queryExists, setQueryExists] = useState(false);
|
||||||
|
const dimensions = useWindowDimensions();
|
||||||
|
const refreshSelection = useCallback(() => {
|
||||||
|
const ids = notes.map((item) => item.id);
|
||||||
|
updateInitialSelectionState(ids).then((selection) => {
|
||||||
|
useTagItemSelection.setState({
|
||||||
|
initialState: selection,
|
||||||
|
selection: { ...selection }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [notes, tags]);
|
||||||
|
|
||||||
|
const refreshTags = useCallback(() => {
|
||||||
|
if (query && query.trim() !== "") {
|
||||||
|
db.lookup.tags(query).then((items) => {
|
||||||
|
setTags(items);
|
||||||
|
console.log("searched tags");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
|
||||||
|
console.log("items loaded tags");
|
||||||
|
setTags(items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshTags();
|
||||||
|
}, [refreshTags, query]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshSelection();
|
||||||
|
}, [refreshSelection]);
|
||||||
|
|
||||||
const checkQueryExists = (query: string) => {
|
const checkQueryExists = (query: string) => {
|
||||||
db.tags.all
|
db.tags.all
|
||||||
@@ -114,6 +179,7 @@ const ManageTagsSheet = (props: {
|
|||||||
|
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
useTagStore.getState().setTags();
|
useTagStore.getState().setTags();
|
||||||
|
refreshTags();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Cannot add tag",
|
heading: "Cannot add tag",
|
||||||
@@ -126,13 +192,64 @@ const ManageTagsSheet = (props: {
|
|||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPress = useCallback(
|
||||||
|
async (id: string) => {
|
||||||
|
for (const note of notes) {
|
||||||
|
try {
|
||||||
|
if (!id) return;
|
||||||
|
const isSelected =
|
||||||
|
useTagItemSelection.getState().initialState[id] === "selected";
|
||||||
|
if (isSelected) {
|
||||||
|
await db.relations.unlink(
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
type: "tag"
|
||||||
|
},
|
||||||
|
note
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await db.relations.add(
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
type: "tag"
|
||||||
|
},
|
||||||
|
note
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useTagStore.getState().setTags();
|
||||||
|
useRelationStore.getState().update();
|
||||||
|
refreshTags();
|
||||||
|
setTimeout(() => {
|
||||||
|
Navigation.queueRoutesForUpdate();
|
||||||
|
}, 1);
|
||||||
|
refreshSelection();
|
||||||
|
},
|
||||||
|
[notes, refreshSelection, refreshTags]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTag = useCallback(
|
||||||
|
({ item }: { item: string; index: number }) => (
|
||||||
|
<TagItem
|
||||||
|
key={item as string}
|
||||||
|
tags={tags as VirtualizedGrouping<Tag>}
|
||||||
|
id={item as string}
|
||||||
|
onPress={onPress}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[onPress, tags]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
minHeight: focus ? "100%" : "60%"
|
maxHeight: dimensions.height * 0.85
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
@@ -161,31 +278,35 @@ const ManageTagsSheet = (props: {
|
|||||||
placeholder="Search or add a tag"
|
placeholder="Search or add a tag"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollView
|
{query && !queryExists ? (
|
||||||
overScrollMode="never"
|
<PressableButton
|
||||||
scrollToOverflowEnabled={false}
|
key={"query_item"}
|
||||||
keyboardDismissMode="none"
|
customStyle={{
|
||||||
keyboardShouldPersistTaps="always"
|
flexDirection: "row",
|
||||||
>
|
marginVertical: 5,
|
||||||
{query && !queryExists ? (
|
justifyContent: "space-between",
|
||||||
<PressableButton
|
padding: 12
|
||||||
key={"query_item"}
|
}}
|
||||||
customStyle={{
|
onPress={onSubmit}
|
||||||
flexDirection: "row",
|
type="selected"
|
||||||
marginVertical: 5,
|
>
|
||||||
justifyContent: "space-between",
|
<Heading size={SIZE.sm} color={colors.selected.heading}>
|
||||||
padding: 12
|
Add {'"' + "#" + query + '"'}
|
||||||
}}
|
</Heading>
|
||||||
onPress={onSubmit}
|
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
||||||
type="selected"
|
</PressableButton>
|
||||||
>
|
) : null}
|
||||||
<Heading size={SIZE.sm} color={colors.selected.heading}>
|
|
||||||
Add {'"' + "#" + query + '"'}
|
<FlatList
|
||||||
</Heading>
|
data={tags?.ids?.filter((id) => typeof id === "string") as string[]}
|
||||||
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
style={{
|
||||||
</PressableButton>
|
width: "100%"
|
||||||
) : null}
|
}}
|
||||||
{!tags || tags.ids.length === 0 ? (
|
keyboardShouldPersistTaps
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
keyExtractor={(item) => item as string}
|
||||||
|
renderItem={renderTag}
|
||||||
|
ListEmptyComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -204,19 +325,9 @@ const ManageTagsSheet = (props: {
|
|||||||
You do not have any tags.
|
You do not have any tags.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
}
|
||||||
|
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||||
{tags?.ids
|
/>
|
||||||
.filter((id) => !isGroupHeader(id))
|
|
||||||
.map((item) => (
|
|
||||||
<TagItem
|
|
||||||
key={item as string}
|
|
||||||
tags={tags}
|
|
||||||
id={item as string}
|
|
||||||
notes={notes}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -233,69 +344,17 @@ export default ManageTagsSheet;
|
|||||||
|
|
||||||
const TagItem = ({
|
const TagItem = ({
|
||||||
id,
|
id,
|
||||||
notes,
|
tags,
|
||||||
tags
|
onPress
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
notes: Note[];
|
|
||||||
tags: VirtualizedGrouping<Tag>;
|
tags: VirtualizedGrouping<Tag>;
|
||||||
|
onPress: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [tag, setTag] = useState<Tag>();
|
const [tag] = useDBItem(id, "tag", tags);
|
||||||
const [selection, setSelection] = useState({
|
const selection = useTagItemSelection((state) => state.selection[id]);
|
||||||
all: false,
|
|
||||||
some: false
|
|
||||||
});
|
|
||||||
const update = useRelationStore((state) => state.updater);
|
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
|
||||||
tags.item(id).then(async (tag) => {
|
|
||||||
if (tag?.id) {
|
|
||||||
setSelection({
|
|
||||||
all: await tagHasAllNotes(
|
|
||||||
tag.id,
|
|
||||||
notes.map((note) => note.id)
|
|
||||||
),
|
|
||||||
some: await tagHasSomeNotes(
|
|
||||||
tag.id,
|
|
||||||
notes.map((note) => note.id)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setTag(tag);
|
|
||||||
});
|
|
||||||
}, [id, tags, notes]);
|
|
||||||
|
|
||||||
if (tag?.id !== id) {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (tag?.id === id) {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}, [id, refresh, tag?.id, update]);
|
|
||||||
|
|
||||||
const onPress = async () => {
|
|
||||||
for (const note of notes) {
|
|
||||||
try {
|
|
||||||
if (!tag?.id) return;
|
|
||||||
if (selection.all) {
|
|
||||||
await db.relations.unlink(tag, note);
|
|
||||||
} else {
|
|
||||||
await db.relations.add(tag, note);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useTagStore.getState().setTags();
|
|
||||||
useRelationStore.getState().update();
|
|
||||||
setTimeout(() => {
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
}, 1);
|
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
customStyle={{
|
customStyle={{
|
||||||
@@ -304,34 +363,32 @@ const TagItem = ({
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
height: 40
|
height: 40
|
||||||
}}
|
}}
|
||||||
onPress={onPress}
|
onPress={() => onPress(id)}
|
||||||
type="gray"
|
type="gray"
|
||||||
>
|
>
|
||||||
{!tag ? null : (
|
{!tag ? null : (
|
||||||
<IconButton
|
<Icon
|
||||||
size={22}
|
size={22}
|
||||||
customStyle={{
|
onPress={() => onPress(id)}
|
||||||
marginRight: 5,
|
|
||||||
width: 23,
|
|
||||||
height: 23
|
|
||||||
}}
|
|
||||||
onPress={onPress}
|
|
||||||
color={
|
color={
|
||||||
selection.some || selection.all
|
selection === "selected" || selection === "intermediate"
|
||||||
? colors.selected.icon
|
? colors.selected.icon
|
||||||
: colors.primary.icon
|
: colors.primary.icon
|
||||||
}
|
}
|
||||||
|
style={{
|
||||||
|
marginRight: 6
|
||||||
|
}}
|
||||||
testID={
|
testID={
|
||||||
selection.all
|
selection === "selected"
|
||||||
? "check-circle-outline"
|
? "check-circle-outline"
|
||||||
: selection.some
|
: selection === "intermediate"
|
||||||
? "minus-circle-outline"
|
? "minus-circle-outline"
|
||||||
: "checkbox-blank-circle-outline"
|
: "checkbox-blank-circle-outline"
|
||||||
}
|
}
|
||||||
name={
|
name={
|
||||||
selection.all
|
selection === "selected"
|
||||||
? "check-circle-outline"
|
? "check-circle-outline"
|
||||||
: selection.some
|
: selection === "intermediate"
|
||||||
? "minus-circle-outline"
|
? "minus-circle-outline"
|
||||||
: "checkbox-blank-circle-outline"
|
: "checkbox-blank-circle-outline"
|
||||||
}
|
}
|
||||||
@@ -344,7 +401,7 @@ const TagItem = ({
|
|||||||
style={{
|
style={{
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 30,
|
height: 30,
|
||||||
backgroundColor: colors.secondary.background,
|
// backgroundColor: colors.secondary.background,
|
||||||
borderRadius: 5
|
borderRadius: 5
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -52,6 +52,24 @@ import Paragraph from "../../ui/typography/paragraph";
|
|||||||
import { AddNotebookSheet } from "../add-notebook";
|
import { AddNotebookSheet } from "../add-notebook";
|
||||||
import Sort from "../sort";
|
import Sort from "../sort";
|
||||||
|
|
||||||
|
const SelectionContext = createContext<{
|
||||||
|
selection: Notebook[];
|
||||||
|
enabled: boolean;
|
||||||
|
setEnabled: (value: boolean) => void;
|
||||||
|
toggleSelection: (item: Notebook) => void;
|
||||||
|
}>({
|
||||||
|
selection: [],
|
||||||
|
enabled: false,
|
||||||
|
setEnabled: (_value: boolean) => {},
|
||||||
|
toggleSelection: (_item: Notebook) => {}
|
||||||
|
});
|
||||||
|
const useSelection = () => useContext(SelectionContext);
|
||||||
|
|
||||||
|
type NotebookParentProp = {
|
||||||
|
parent?: NotebookParentProp;
|
||||||
|
item?: Notebook;
|
||||||
|
};
|
||||||
|
|
||||||
type ConfigItem = { id: string; type: string };
|
type ConfigItem = { id: string; type: string };
|
||||||
class NotebookSheetConfig {
|
class NotebookSheetConfig {
|
||||||
static storageKey: "$$sp";
|
static storageKey: "$$sp";
|
||||||
@@ -88,8 +106,10 @@ const useNotebookExpandedStore = create<{
|
|||||||
|
|
||||||
export const NotebookSheet = () => {
|
export const NotebookSheet = () => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
const currentRoute = useNavigationStore((state) => state.currentRoute);
|
||||||
const canShow = currentScreen.name === "Notebook";
|
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
|
||||||
|
|
||||||
|
const canShow = currentRoute === "Notebook";
|
||||||
const [selection, setSelection] = useState<Notebook[]>([]);
|
const [selection, setSelection] = useState<Notebook[]>([]);
|
||||||
const [enabled, setEnabled] = useState(false);
|
const [enabled, setEnabled] = useState(false);
|
||||||
const { colors } = useThemeColors("sheet");
|
const { colors } = useThemeColors("sheet");
|
||||||
@@ -103,7 +123,7 @@ export const NotebookSheet = () => {
|
|||||||
nestedNotebooks: notebooks,
|
nestedNotebooks: notebooks,
|
||||||
nestedNotebookNotesCount: totalNotes,
|
nestedNotebookNotesCount: totalNotes,
|
||||||
groupOptions
|
groupOptions
|
||||||
} = useNotebook(currentScreen.name === "Notebook" ? root : undefined);
|
} = useNotebook(currentRoute === "Notebook" ? root : undefined);
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
const PLACEHOLDER_DATA = {
|
||||||
heading: "Notebooks",
|
heading: "Notebooks",
|
||||||
@@ -158,8 +178,8 @@ export const NotebookSheet = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canShow) {
|
if (canShow) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const id = currentScreen?.id;
|
if (!focusedRouteId) return;
|
||||||
const nextRoot = await findRootNotebookId(id);
|
const nextRoot = await findRootNotebookId(focusedRouteId);
|
||||||
setRoot(nextRoot);
|
setRoot(nextRoot);
|
||||||
if (nextRoot !== currentItem.current) {
|
if (nextRoot !== currentItem.current) {
|
||||||
setSelection([]);
|
setSelection([]);
|
||||||
@@ -183,7 +203,7 @@ export const NotebookSheet = () => {
|
|||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
ref.current?.hide();
|
ref.current?.hide();
|
||||||
}
|
}
|
||||||
}, [canShow, currentScreen?.id, currentScreen.name, onRequestUpdate]);
|
}, [canShow, currentRoute, onRequestUpdate, focusedRouteId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
@@ -206,7 +226,7 @@ export const NotebookSheet = () => {
|
|||||||
NotebookSheetConfig.set(
|
NotebookSheetConfig.set(
|
||||||
{
|
{
|
||||||
type: "notebook",
|
type: "notebook",
|
||||||
id: currentScreen.id as string
|
id: focusedRouteId as string
|
||||||
},
|
},
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
@@ -392,24 +412,6 @@ export const NotebookSheet = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SelectionContext = createContext<{
|
|
||||||
selection: Notebook[];
|
|
||||||
enabled: boolean;
|
|
||||||
setEnabled: (value: boolean) => void;
|
|
||||||
toggleSelection: (item: Notebook) => void;
|
|
||||||
}>({
|
|
||||||
selection: [],
|
|
||||||
enabled: false,
|
|
||||||
setEnabled: (_value: boolean) => {},
|
|
||||||
toggleSelection: (_item: Notebook) => {}
|
|
||||||
});
|
|
||||||
const useSelection = () => useContext(SelectionContext);
|
|
||||||
|
|
||||||
type NotebookParentProp = {
|
|
||||||
parent?: NotebookParentProp;
|
|
||||||
item?: Notebook;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NotebookItem = ({
|
const NotebookItem = ({
|
||||||
id,
|
id,
|
||||||
totalNotes,
|
totalNotes,
|
||||||
@@ -430,12 +432,12 @@ const NotebookItem = ({
|
|||||||
nestedNotebooks,
|
nestedNotebooks,
|
||||||
notebook: item
|
notebook: item
|
||||||
} = useNotebook(id, items);
|
} = useNotebook(id, items);
|
||||||
const screen = useNavigationStore((state) => state.currentScreen);
|
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
|
||||||
const { colors } = useThemeColors("sheet");
|
const { colors } = useThemeColors("sheet");
|
||||||
const selection = useSelection();
|
const selection = useSelection();
|
||||||
const isSelected =
|
const isSelected =
|
||||||
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
|
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
|
||||||
const isFocused = screen.id === id;
|
|
||||||
const { fontScale } = useWindowDimensions();
|
const { fontScale } = useWindowDimensions();
|
||||||
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ const Sort = ({ type, screen }) => {
|
|||||||
db.settings.getGroupOptions(screen === "Notes" ? "home" : type + "s")
|
db.settings.getGroupOptions(screen === "Notes" ? "home" : type + "s")
|
||||||
);
|
);
|
||||||
const updateGroupOptions = async (_groupOptions) => {
|
const updateGroupOptions = async (_groupOptions) => {
|
||||||
await db.settings.setGroupOptions(type, _groupOptions);
|
const groupType = screen === "Notes" ? "home" : type + "s";
|
||||||
|
console.log("updateGroupOptions for group", groupType, "in", screen);
|
||||||
|
await db.settings.setGroupOptions(groupType, _groupOptions);
|
||||||
setGroupOptions(_groupOptions);
|
setGroupOptions(_groupOptions);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
|
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const ColorItem = React.memo(
|
|||||||
const onHeaderStateChange = useCallback(
|
const onHeaderStateChange = useCallback(
|
||||||
(state: any) => {
|
(state: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let id = state.currentScreen?.id;
|
let id = state.focusedRouteId;
|
||||||
if (id === item.id) {
|
if (id === item.id) {
|
||||||
setHeaderTextState({ id: state.currentScreen.id });
|
setHeaderTextState({ id: state.currentScreen.id });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,10 +34,9 @@ export const MenuItem = React.memo(
|
|||||||
function MenuItem({ item, index, testID, rightBtn }) {
|
function MenuItem({ item, index, testID, rightBtn }) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [headerTextState, setHeaderTextState] = useState(
|
const [headerTextState, setHeaderTextState] = useState(
|
||||||
useNavigationStore.getState().currentScreen
|
useNavigationStore.getState().focusedRouteId
|
||||||
);
|
);
|
||||||
const screenId = item.name.toLowerCase() + "_navigation";
|
let isFocused = headerTextState?.id === item.name;
|
||||||
let isFocused = headerTextState?.id === screenId;
|
|
||||||
const primaryColors = isFocused ? colors.selected : colors.primary;
|
const primaryColors = isFocused ? colors.selected : colors.primary;
|
||||||
|
|
||||||
const _onPress = () => {
|
const _onPress = () => {
|
||||||
@@ -59,9 +58,9 @@ export const MenuItem = React.memo(
|
|||||||
const onHeaderStateChange = useCallback(
|
const onHeaderStateChange = useCallback(
|
||||||
(state) => {
|
(state) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let id = state.currentScreen?.id;
|
let id = state.focusedRouteId;
|
||||||
if (id === screenId) {
|
if (id === item.name) {
|
||||||
setHeaderTextState({ id: state.currentScreen.id });
|
setHeaderTextState({ id: state.focusedRouteId });
|
||||||
} else {
|
} else {
|
||||||
if (headerTextState !== null) {
|
if (headerTextState !== null) {
|
||||||
setHeaderTextState(null);
|
setHeaderTextState(null);
|
||||||
@@ -69,7 +68,7 @@ export const MenuItem = React.memo(
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
[headerTextState, screenId]
|
[headerTextState, item.name]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import { Notebook, Tag } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { FlatList, View } from "react-native";
|
import { FlatList, View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
|
import { db } from "../../common/database";
|
||||||
import NotebookScreen from "../../screens/notebook";
|
import NotebookScreen from "../../screens/notebook";
|
||||||
import { TaggedNotes } from "../../screens/notes/tagged";
|
import { TaggedNotes } from "../../screens/notes/tagged";
|
||||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
import { useMenuStore } from "../../stores/use-menu-store";
|
import { useMenuStore } from "../../stores/use-menu-store";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useNoteStore } from "../../stores/use-notes-store";
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { SIZE, normalize } from "../../utils/size";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import { normalize, SIZE } from "../../utils/size";
|
|
||||||
import { Properties } from "../properties";
|
import { Properties } from "../properties";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Notice } from "../ui/notice";
|
import { Notice } from "../ui/notice";
|
||||||
@@ -38,8 +38,6 @@ import Seperator from "../ui/seperator";
|
|||||||
import SheetWrapper from "../ui/sheet";
|
import SheetWrapper from "../ui/sheet";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { useCallback } from "react";
|
|
||||||
import { Notebook, Tag } from "@notesnook/core";
|
|
||||||
|
|
||||||
export const TagsSection = React.memo(
|
export const TagsSection = React.memo(
|
||||||
function TagsSection() {
|
function TagsSection() {
|
||||||
@@ -127,7 +125,7 @@ export const PinItem = React.memo(
|
|||||||
const onHeaderStateChange = useCallback(
|
const onHeaderStateChange = useCallback(
|
||||||
(state: any) => {
|
(state: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const id = state.currentScreen?.id;
|
const id = state.focusedRouteId;
|
||||||
if (id === item.id) {
|
if (id === item.id) {
|
||||||
setHeaderTextState({
|
setHeaderTextState({
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function showAttachments() {
|
async function showAttachments() {
|
||||||
AttachmentDialog.present(item);
|
AttachmentDialog.present(item as Note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportNote() {
|
async function exportNote() {
|
||||||
@@ -486,12 +486,10 @@ export const useActions = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function removeNoteFromNotebook() {
|
async function removeNoteFromNotebook() {
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
|
||||||
if (currentScreen.name !== "Notebook") return;
|
if (currentRoute !== "Notebook" || !focusedRouteId) return;
|
||||||
await db.relations.unlink(
|
|
||||||
{ type: "notebook", id: currentScreen.id },
|
await db.relations.unlink({ type: "notebook", id: focusedRouteId }, item);
|
||||||
item
|
|
||||||
);
|
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -499,7 +497,7 @@ export const useActions = ({
|
|||||||
function addTo() {
|
function addTo() {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
setSelectedItem(item);
|
setSelectedItem(item);
|
||||||
MoveNoteSheet.present(item);
|
MoveNoteSheet.present(item as Note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addToFavorites() {
|
async function addToFavorites() {
|
||||||
@@ -857,11 +855,13 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
|
||||||
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
if (item.type !== "note" || currentRoute !== "Notebook" || !focusedRouteId)
|
||||||
|
return;
|
||||||
|
|
||||||
!!db.relations
|
!!db.relations
|
||||||
.to(item, "notebook")
|
.to(item, "notebook")
|
||||||
.selector.find((v) => v("id", "==", currentScreen.id))
|
.selector.find((v) => v("id", "==", focusedRouteId))
|
||||||
.then((notebook) => {
|
.then((notebook) => {
|
||||||
setNoteInCurrentNotebook(!!notebook);
|
setNoteInCurrentNotebook(!!notebook);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,9 +56,12 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
|||||||
const onUpdateItem = (itemId?: string) => {
|
const onUpdateItem = (itemId?: string) => {
|
||||||
if (typeof itemId === "string" && itemId !== id) return;
|
if (typeof itemId === "string" && itemId !== id) return;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
setItem(undefined);
|
if (item) {
|
||||||
|
setItem(undefined);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log("onUpdateItem", id, type);
|
||||||
|
|
||||||
if (items) {
|
if (items) {
|
||||||
items.item(id).then((item) => {
|
items.item(id).then((item) => {
|
||||||
@@ -82,7 +85,7 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
|||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||||
};
|
};
|
||||||
}, [id, type]);
|
}, [id, type, items, item]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
item as ItemTypeKey[T],
|
item as ItemTypeKey[T],
|
||||||
@@ -116,6 +119,7 @@ export const useTotalNotes = (
|
|||||||
}
|
}
|
||||||
setTotalNotesById(totalNotesById);
|
setTotalNotesById(totalNotesById);
|
||||||
});
|
});
|
||||||
|
console.log("useTotalNotes.getTotalNotes");
|
||||||
}, [ids, type]);
|
}, [ids, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
|
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { useRoute } from "@react-navigation/core";
|
||||||
|
import useNavigationStore, { RouteName } from "../stores/use-navigation-store";
|
||||||
|
|
||||||
type NavigationFocus = {
|
type NavigationFocus = {
|
||||||
onFocus?: (prev: RefObject<boolean>) => boolean;
|
onFocus?: (prev: RefObject<boolean>) => boolean;
|
||||||
@@ -31,6 +33,7 @@ export const useNavigationFocus = (
|
|||||||
navigation: NativeStackNavigationProp<Record<string, object | undefined>>,
|
navigation: NativeStackNavigationProp<Record<string, object | undefined>>,
|
||||||
{ onFocus, onBlur, delay, focusOnInit = true }: NavigationFocus
|
{ onFocus, onBlur, delay, focusOnInit = true }: NavigationFocus
|
||||||
) => {
|
) => {
|
||||||
|
const route = useRoute();
|
||||||
const [isFocused, setFocused] = useState(focusOnInit);
|
const [isFocused, setFocused] = useState(focusOnInit);
|
||||||
const prev = useRef(false);
|
const prev = useRef(false);
|
||||||
const isBlurred = useRef(false);
|
const isBlurred = useRef(false);
|
||||||
@@ -39,6 +42,12 @@ export const useNavigationFocus = (
|
|||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
() => {
|
||||||
const shouldFocus = onFocus ? onFocus(prev) : true;
|
const shouldFocus = onFocus ? onFocus(prev) : true;
|
||||||
|
|
||||||
|
const routeName = route.name?.startsWith("Settings")
|
||||||
|
? "Settings"
|
||||||
|
: route.name;
|
||||||
|
useNavigationStore.getState().update(routeName as RouteName);
|
||||||
|
|
||||||
if (shouldFocus) {
|
if (shouldFocus) {
|
||||||
setFocused(true);
|
setFocused(true);
|
||||||
prev.current = true;
|
prev.current = true;
|
||||||
|
|||||||
@@ -39,17 +39,19 @@ export const useNotebook = (
|
|||||||
|
|
||||||
const onRequestUpdate = React.useCallback(() => {
|
const onRequestUpdate = React.useCallback(() => {
|
||||||
if (!item || !id) {
|
if (!item || !id) {
|
||||||
console.log("unset notebook");
|
if (notebooks) {
|
||||||
setNotebooks(undefined);
|
setNotebooks(undefined);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log("useNotebook.onRequestUpdate", id);
|
||||||
db.relations
|
db.relations
|
||||||
.from(item, "notebook")
|
.from(item, "notebook")
|
||||||
.selector.sorted(db.settings.getGroupOptions("notebooks"))
|
.selector.sorted(db.settings.getGroupOptions("notebooks"))
|
||||||
.then((notebooks) => {
|
.then((notebooks) => {
|
||||||
setNotebooks(notebooks);
|
setNotebooks(notebooks);
|
||||||
});
|
});
|
||||||
}, [item, id]);
|
}, [item, id, notebooks]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onRequestUpdate();
|
onRequestUpdate();
|
||||||
@@ -75,7 +77,7 @@ export const useNotebook = (
|
|||||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||||
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
||||||
};
|
};
|
||||||
}, [onUpdate, onRequestUpdate, id]);
|
}, [onUpdate, onRequestUpdate, id, refresh]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notebook: item,
|
notebook: item,
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import Notebooks from "../screens/notebooks";
|
|||||||
import { ColoredNotes } from "../screens/notes/colored";
|
import { ColoredNotes } from "../screens/notes/colored";
|
||||||
import { Monographs } from "../screens/notes/monographs";
|
import { Monographs } from "../screens/notes/monographs";
|
||||||
import { TaggedNotes } from "../screens/notes/tagged";
|
import { TaggedNotes } from "../screens/notes/tagged";
|
||||||
import { TopicNotes } from "../screens/notes/topic-notes";
|
|
||||||
import Reminders from "../screens/reminders";
|
import Reminders from "../screens/reminders";
|
||||||
import { Search } from "../screens/search";
|
import { Search } from "../screens/search";
|
||||||
import Settings from "../screens/settings";
|
import Settings from "../screens/settings";
|
||||||
@@ -120,44 +119,14 @@ const _Tabs = () => {
|
|||||||
<NativeStack.Screen name="Welcome" component={IntroStackNavigator} />
|
<NativeStack.Screen name="Welcome" component={IntroStackNavigator} />
|
||||||
<NativeStack.Screen name="Notes" component={Home} />
|
<NativeStack.Screen name="Notes" component={Home} />
|
||||||
<NativeStack.Screen name="Notebooks" component={Notebooks} />
|
<NativeStack.Screen name="Notebooks" component={Notebooks} />
|
||||||
<NativeStack.Screen
|
<NativeStack.Screen name="Favorites" component={Favorites} />
|
||||||
options={{ lazy: true }}
|
<NativeStack.Screen name="Trash" component={Trash} />
|
||||||
name="Favorites"
|
<NativeStack.Screen name="Tags" component={Tags} />
|
||||||
component={Favorites}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="Trash"
|
|
||||||
component={Trash}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="Tags"
|
|
||||||
component={Tags}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen name="Settings" component={Settings} />
|
<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
|
<NativeStack.Screen
|
||||||
options={{ lazy: true }}
|
|
||||||
name="TaggedNotes"
|
|
||||||
component={TaggedNotes}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="TopicNotes"
|
|
||||||
component={TopicNotes}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="ColoredNotes"
|
|
||||||
component={ColoredNotes}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="Reminders"
|
|
||||||
component={Reminders}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="Monographs"
|
name="Monographs"
|
||||||
initialParams={{
|
initialParams={{
|
||||||
item: { type: "monograph" },
|
item: { type: "monograph" },
|
||||||
@@ -166,16 +135,8 @@ const _Tabs = () => {
|
|||||||
}}
|
}}
|
||||||
component={Monographs}
|
component={Monographs}
|
||||||
/>
|
/>
|
||||||
<NativeStack.Screen
|
<NativeStack.Screen name="Notebook" component={NotebookScreen} />
|
||||||
options={{ lazy: true }}
|
<NativeStack.Screen name="Search" component={Search} />
|
||||||
name="Notebook"
|
|
||||||
component={NotebookScreen}
|
|
||||||
/>
|
|
||||||
<NativeStack.Screen
|
|
||||||
options={{ lazy: true }}
|
|
||||||
name="Search"
|
|
||||||
component={Search}
|
|
||||||
/>
|
|
||||||
</NativeStack.Navigator>
|
</NativeStack.Navigator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import SettingsService from "../../services/settings";
|
|||||||
import { useFavoriteStore } from "../../stores/use-favorite-store";
|
import { useFavoriteStore } from "../../stores/use-favorite-store";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useNoteStore } from "../../stores/use-notes-store";
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
const prepareSearch = () => {
|
const prepareSearch = () => {
|
||||||
SearchService.update({
|
SearchService.update({
|
||||||
placeholder: "Search in favorites",
|
placeholder: "Search in favorites",
|
||||||
@@ -50,9 +51,7 @@ export const Favorites = ({
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId(route?.name);
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
SearchService.prepareSearch = prepareSearch;
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
@@ -61,23 +60,43 @@ export const Favorites = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout wait={loading}>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={favorites}
|
renderedInRoute={route.name}
|
||||||
dataType="note"
|
title={route.name}
|
||||||
onRefresh={() => {
|
canGoBack={false}
|
||||||
setFavorites();
|
hasSearch={true}
|
||||||
|
id={route.name}
|
||||||
|
onSearch={() => {
|
||||||
|
Navigation.push("Search", {
|
||||||
|
placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
|
||||||
|
type: "note",
|
||||||
|
title: route.name,
|
||||||
|
route: route.name,
|
||||||
|
ids: favorites?.ids.filter(
|
||||||
|
(id) => typeof id === "string"
|
||||||
|
) as string[]
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
renderedInRoute="Favorites"
|
|
||||||
loading={loading || !isFocused}
|
|
||||||
placeholder={{
|
|
||||||
title: "Your favorites",
|
|
||||||
paragraph: "You have not added any notes to favorites yet.",
|
|
||||||
loading: "Loading your favorites"
|
|
||||||
}}
|
|
||||||
headerTitle="Favorites"
|
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
<DelayLayout wait={loading}>
|
||||||
|
<List
|
||||||
|
data={favorites}
|
||||||
|
dataType="note"
|
||||||
|
onRefresh={() => {
|
||||||
|
setFavorites();
|
||||||
|
}}
|
||||||
|
renderedInRoute="Favorites"
|
||||||
|
loading={loading || !isFocused}
|
||||||
|
placeholder={{
|
||||||
|
title: "Your favorites",
|
||||||
|
paragraph: "You have not added any notes to favorites yet.",
|
||||||
|
loading: "Loading your favorites"
|
||||||
|
}}
|
||||||
|
headerTitle="Favorites"
|
||||||
|
/>
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,26 +18,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import { FloatingButton } from "../../components/container/floating-button";
|
import { FloatingButton } from "../../components/container/floating-button";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
|
||||||
import { useNoteStore } from "../../stores/use-notes-store";
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { openEditor } from "../notes/common";
|
import { openEditor } from "../notes/common";
|
||||||
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
const prepareSearch = () => {
|
|
||||||
SearchService.update({
|
|
||||||
placeholder: "Type a keyword to search in notes",
|
|
||||||
type: "notes",
|
|
||||||
title: "Notes",
|
|
||||||
get: () => db.notes?.all
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||||
const notes = useNoteStore((state) => state.notes);
|
const notes = useNoteStore((state) => state.notes);
|
||||||
@@ -48,11 +38,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
|
||||||
useNavigationStore.getState().setButtonAction(openEditor);
|
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
onBlur: () => false,
|
onBlur: () => false,
|
||||||
@@ -60,23 +46,41 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout wait={loading} delay={500}>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={notes}
|
renderedInRoute={route.name}
|
||||||
dataType="note"
|
title={route.name}
|
||||||
renderedInRoute="Notes"
|
canGoBack={false}
|
||||||
loading={loading || !isFocused}
|
hasSearch={true}
|
||||||
headerTitle="Notes"
|
onSearch={() => {
|
||||||
placeholder={{
|
Navigation.push("Search", {
|
||||||
title: "Notes",
|
placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
|
||||||
paragraph: "You have not added any notes yet.",
|
type: "note",
|
||||||
button: "Add your first note",
|
title: route.name,
|
||||||
action: openEditor,
|
route: route.name
|
||||||
loading: "Loading your notes"
|
});
|
||||||
}}
|
}}
|
||||||
|
id={route.name}
|
||||||
|
onPressDefaultRightButton={openEditor}
|
||||||
/>
|
/>
|
||||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
<DelayLayout wait={loading} delay={500}>
|
||||||
</DelayLayout>
|
<List
|
||||||
|
data={notes}
|
||||||
|
dataType="note"
|
||||||
|
renderedInRoute={route.name}
|
||||||
|
loading={loading || !isFocused}
|
||||||
|
headerTitle={route.name}
|
||||||
|
placeholder={{
|
||||||
|
title: route.name?.toLowerCase(),
|
||||||
|
paragraph: `You have not added any ${route.name.toLowerCase()} yet.`,
|
||||||
|
button: "Add your first note",
|
||||||
|
action: openEditor,
|
||||||
|
loading: "Loading your notes"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { Note, Notebook } from "@notesnook/core/dist/types";
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
||||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||||
@@ -30,7 +31,6 @@ import {
|
|||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import useNavigationStore, {
|
import useNavigationStore, {
|
||||||
NotebookScreenParams
|
NotebookScreenParams
|
||||||
} from "../../stores/use-navigation-store";
|
} from "../../stores/use-navigation-store";
|
||||||
@@ -46,7 +46,6 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
useNavigationStore.getState().setButtonAction(openEditor);
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
@@ -56,21 +55,12 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const syncWithNavigation = React.useCallback(() => {
|
const syncWithNavigation = React.useCallback(() => {
|
||||||
useNavigationStore.getState().update(
|
useNavigationStore.getState().setFocusedRouteId(params?.current?.item?.id);
|
||||||
{
|
|
||||||
name: route.name,
|
|
||||||
title: params.current?.title,
|
|
||||||
id: params.current?.item?.id,
|
|
||||||
type: "notebook"
|
|
||||||
},
|
|
||||||
params.current?.canGoBack
|
|
||||||
);
|
|
||||||
setOnFirstSave({
|
setOnFirstSave({
|
||||||
type: "notebook",
|
type: "notebook",
|
||||||
id: params.current.item.id
|
id: params.current.item.id
|
||||||
});
|
});
|
||||||
SearchService.prepareSearch = prepareSearch;
|
}, []);
|
||||||
}, [route.name]);
|
|
||||||
|
|
||||||
const onRequestUpdate = React.useCallback(
|
const onRequestUpdate = React.useCallback(
|
||||||
async (data?: NotebookScreenParams) => {
|
async (data?: NotebookScreenParams) => {
|
||||||
@@ -111,44 +101,25 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const prepareSearch = () => {
|
|
||||||
// SearchService.update({
|
|
||||||
// placeholder: `Search in "${params.current.title}"`,
|
|
||||||
// type: "notes",
|
|
||||||
// title: params.current.title,
|
|
||||||
// get: () => {
|
|
||||||
// const notebook = db.notebooks?.notebook(
|
|
||||||
// params?.current?.item?.id
|
|
||||||
// )?.data;
|
|
||||||
// if (!notebook) return [];
|
|
||||||
// const notes = db.relations?.from(notebook, "note") || [];
|
|
||||||
// const topicNotes = db.notebooks
|
|
||||||
// .notebook(notebook.id)
|
|
||||||
// ?.topics.all.map((topic: Topic) => {
|
|
||||||
// return db.notes?.topicReferences
|
|
||||||
// .get(topic.id)
|
|
||||||
// .map((id: string) => db.notes?.note(id)?.data);
|
|
||||||
// })
|
|
||||||
// .flat()
|
|
||||||
// .filter(
|
|
||||||
// (topicNote) =>
|
|
||||||
// notes.findIndex((note) => note?.id !== topicNote?.id) === -1
|
|
||||||
// ) as Note[];
|
|
||||||
// return [...notes, ...topicNotes];
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
title: params.current.item?.title,
|
|
||||||
paragraph: "You have not added any notes yet.",
|
|
||||||
button: "Add your first note",
|
|
||||||
action: openEditor,
|
|
||||||
loading: "Loading notebook notes"
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Header
|
||||||
|
renderedInRoute={route.name}
|
||||||
|
title={params.current.item?.title}
|
||||||
|
canGoBack={params?.current?.canGoBack}
|
||||||
|
hasSearch={true}
|
||||||
|
onSearch={() => {
|
||||||
|
Navigation.push("Search", {
|
||||||
|
placeholder: `Type a keyword to search in ${params.current.item?.title}`,
|
||||||
|
type: "note",
|
||||||
|
title: params.current.item?.title,
|
||||||
|
route: route.name,
|
||||||
|
ids: notes?.ids.filter((id) => typeof id === "string") as string[]
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
id={params.current.item?.id}
|
||||||
|
onPressDefaultRightButton={openEditor}
|
||||||
|
/>
|
||||||
<DelayLayout>
|
<DelayLayout>
|
||||||
<List
|
<List
|
||||||
data={notes}
|
data={notes}
|
||||||
@@ -170,7 +141,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
placeholder={PLACEHOLDER_DATA}
|
placeholder={{
|
||||||
|
title: params.current.item?.title,
|
||||||
|
paragraph: "You have not added any notes yet.",
|
||||||
|
button: "Add your first note",
|
||||||
|
action: openEditor,
|
||||||
|
loading: "Loading notebook notes"
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
</>
|
</>
|
||||||
@@ -179,19 +156,11 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
|
|
||||||
NotebookScreen.navigate = (item: Notebook, canGoBack?: boolean) => {
|
NotebookScreen.navigate = (item: Notebook, canGoBack?: boolean) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Navigation.navigate<"Notebook">(
|
Navigation.navigate<"Notebook">("Notebook", {
|
||||||
{
|
title: item.title,
|
||||||
title: item.title,
|
item: item,
|
||||||
name: "Notebook",
|
canGoBack
|
||||||
id: item.id,
|
});
|
||||||
type: "notebook"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: item.title,
|
|
||||||
item: item,
|
|
||||||
canGoBack
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotebookScreen;
|
export default NotebookScreen;
|
||||||
|
|||||||
@@ -19,32 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Config } from "react-native-config";
|
import { Config } from "react-native-config";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import { FloatingButton } from "../../components/container/floating-button";
|
import { FloatingButton } from "../../components/container/floating-button";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||||
import { Walkthrough } from "../../components/walkthroughs";
|
import { Walkthrough } from "../../components/walkthroughs";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useNotebookStore } from "../../stores/use-notebook-store";
|
import { useNotebookStore } from "../../stores/use-notebook-store";
|
||||||
|
|
||||||
const onPressFloatingButton = () => {
|
const onButtonPress = () => {
|
||||||
AddNotebookSheet.present();
|
AddNotebookSheet.present();
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepareSearch = () => {
|
|
||||||
SearchService.update({
|
|
||||||
placeholder: "Type a keyword to search in notebooks",
|
|
||||||
type: "notebooks",
|
|
||||||
title: "Notebooks",
|
|
||||||
get: () => db.notebooks?.all
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Notebooks = ({
|
export const Notebooks = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -56,12 +46,7 @@ export const Notebooks = ({
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
|
||||||
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
|
||||||
|
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
onBlur: () => false,
|
onBlur: () => false,
|
||||||
@@ -79,29 +64,47 @@ export const Notebooks = ({
|
|||||||
}, [notebooks]);
|
}, [notebooks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout delay={1}>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={notebooks}
|
renderedInRoute={route.name}
|
||||||
dataType="notebook"
|
title={route.name}
|
||||||
renderedInRoute="Notebooks"
|
canGoBack={route.params?.canGoBack}
|
||||||
loading={!isFocused}
|
hasSearch={true}
|
||||||
placeholder={{
|
id={route.name}
|
||||||
title: "Your notebooks",
|
onSearch={() => {
|
||||||
paragraph: "You have not added any notebooks yet.",
|
Navigation.push("Search", {
|
||||||
button: "Add your first notebook",
|
placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
|
||||||
action: onPressFloatingButton,
|
type: "notebook",
|
||||||
loading: "Loading your notebooks"
|
title: route.name,
|
||||||
|
route: route.name
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
headerTitle="Notebooks"
|
onPressDefaultRightButton={onButtonPress}
|
||||||
/>
|
/>
|
||||||
|
<DelayLayout delay={1}>
|
||||||
{!notebooks || notebooks.ids.length === 0 || !isFocused ? null : (
|
<List
|
||||||
<FloatingButton
|
data={notebooks}
|
||||||
title="Create a new notebook"
|
dataType="notebook"
|
||||||
onPress={onPressFloatingButton}
|
renderedInRoute="Notebooks"
|
||||||
|
loading={!isFocused}
|
||||||
|
placeholder={{
|
||||||
|
title: "Your notebooks",
|
||||||
|
paragraph: "You have not added any notebooks yet.",
|
||||||
|
button: "Add your first notebook",
|
||||||
|
action: onButtonPress,
|
||||||
|
loading: "Loading your notebooks"
|
||||||
|
}}
|
||||||
|
headerTitle="Notebooks"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</DelayLayout>
|
{!notebooks || notebooks.ids.length === 0 || !isFocused ? null : (
|
||||||
|
<FloatingButton
|
||||||
|
title="Create a new notebook"
|
||||||
|
onPress={onButtonPress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Color } from "@notesnook/core/dist/types";
|
import { Color } from "@notesnook/core/dist/types";
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||||
import { openEditor, toCamelCase } from "./common";
|
import { PLACEHOLDER_DATA, openEditor, toCamelCase } from "./common";
|
||||||
export const ColoredNotes = ({
|
export const ColoredNotes = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -54,18 +53,9 @@ ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
|||||||
|
|
||||||
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Navigation.navigate<"ColoredNotes">(
|
Navigation.navigate<"ColoredNotes">("ColoredNotes", {
|
||||||
{
|
item: item,
|
||||||
name: "ColoredNotes",
|
canGoBack,
|
||||||
title: toCamelCase(item.title),
|
title: toCamelCase(item.title)
|
||||||
id: item.id,
|
});
|
||||||
type: "color",
|
|
||||||
color: item.title?.toLowerCase()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
item: item,
|
|
||||||
canGoBack,
|
|
||||||
title: toCamelCase(item.title)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ import { openLinkInBrowser } from "../../utils/functions";
|
|||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { editorController, editorState } from "../editor/tiptap/utils";
|
import { editorController, editorState } from "../editor/tiptap/utils";
|
||||||
|
|
||||||
|
export const PLACEHOLDER_DATA = {
|
||||||
|
title: "Your notes",
|
||||||
|
paragraph: "You have not added any notes yet.",
|
||||||
|
button: "Add your first Note",
|
||||||
|
action: openEditor,
|
||||||
|
loading: "Loading your notes."
|
||||||
|
};
|
||||||
|
|
||||||
export function toCamelCase(title: string) {
|
export function toCamelCase(title: string) {
|
||||||
if (!title) return "";
|
if (!title) return "";
|
||||||
return title.slice(0, 1).toUpperCase() + title.slice(1);
|
return title.slice(0, 1).toUpperCase() + title.slice(1);
|
||||||
|
|||||||
@@ -17,21 +17,14 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
Color,
|
import { Color, Note } from "@notesnook/core/dist/types";
|
||||||
GroupedItems,
|
|
||||||
Item,
|
|
||||||
Note,
|
|
||||||
Topic
|
|
||||||
} from "@notesnook/core/dist/types";
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
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 { FloatingButton } from "../../components/container/floating-button";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { IconButton } from "../../components/ui/icon-button";
|
import { PlaceholderData } from "../../components/list/empty";
|
||||||
import Paragraph from "../../components/ui/typography/paragraph";
|
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import {
|
import {
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
@@ -45,39 +38,11 @@ import useNavigationStore, {
|
|||||||
RouteName
|
RouteName
|
||||||
} from "../../stores/use-navigation-store";
|
} from "../../stores/use-navigation-store";
|
||||||
import { useNoteStore } from "../../stores/use-notes-store";
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { SIZE } from "../../utils/size";
|
import { setOnFirstSave } from "./common";
|
||||||
|
|
||||||
import NotebookScreen from "../notebook/index";
|
|
||||||
import {
|
|
||||||
openEditor,
|
|
||||||
openMonographsWebpage,
|
|
||||||
setOnFirstSave,
|
|
||||||
toCamelCase
|
|
||||||
} from "./common";
|
|
||||||
import { PlaceholderData } from "../../components/list/empty";
|
|
||||||
import { VirtualizedGrouping } from "@notesnook/core";
|
|
||||||
export const WARNING_DATA = {
|
export const WARNING_DATA = {
|
||||||
title: "Some notes in this topic are not synced"
|
title: "Some notes in this topic are not synced"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PLACEHOLDER_DATA = {
|
|
||||||
title: "Your notes",
|
|
||||||
paragraph: "You have not added any notes yet.",
|
|
||||||
button: "Add your first Note",
|
|
||||||
action: openEditor,
|
|
||||||
loading: "Loading your notes."
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MONOGRAPH_PLACEHOLDER_DATA = {
|
|
||||||
heading: "Your monographs",
|
|
||||||
paragraph: "You have not published any notes as monographs yet.",
|
|
||||||
button: "Learn more about monographs",
|
|
||||||
action: openMonographsWebpage,
|
|
||||||
loading: "Loading published notes.",
|
|
||||||
type: "monographs",
|
|
||||||
buttonIcon: "information-outline"
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
||||||
get: (
|
get: (
|
||||||
params: NotesScreenParams,
|
params: NotesScreenParams,
|
||||||
@@ -93,7 +58,6 @@ export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
|||||||
function getItemType(routeName: RouteName) {
|
function getItemType(routeName: RouteName) {
|
||||||
if (routeName === "TaggedNotes") return "tag";
|
if (routeName === "TaggedNotes") return "tag";
|
||||||
if (routeName === "ColoredNotes") return "color";
|
if (routeName === "ColoredNotes") return "color";
|
||||||
if (routeName === "TopicNotes") return "topic";
|
|
||||||
if (routeName === "Monographs") return "monograph";
|
if (routeName === "Monographs") return "monograph";
|
||||||
return "note";
|
return "note";
|
||||||
}
|
}
|
||||||
@@ -110,24 +74,24 @@ const NotesPage = ({
|
|||||||
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
||||||
>) => {
|
>) => {
|
||||||
const params = useRef<NotesScreenParams>(route?.params);
|
const params = useRef<NotesScreenParams>(route?.params);
|
||||||
|
|
||||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||||
|
|
||||||
const loading = useNoteStore((state) => state.loading);
|
const loading = useNoteStore((state) => state.loading);
|
||||||
const [loadingNotes, setLoadingNotes] = useState(true);
|
const [loadingNotes, setLoadingNotes] = useState(true);
|
||||||
const isMonograph = route.name === "Monographs";
|
const isMonograph = route.name === "Monographs";
|
||||||
|
const title =
|
||||||
// const notebook =
|
params.current?.item.type === "tag"
|
||||||
// route.name === "TopicNotes" &&
|
? "#" + params.current?.item.title
|
||||||
// params.current.item.type === "topic" &&
|
: params.current?.item.title;
|
||||||
// params.current.item.notebookId
|
const accentColor =
|
||||||
// ? db.notebooks?.notebook((params.current.item as Topic).notebookId)?.data
|
route.name === "ColoredNotes"
|
||||||
// : null;
|
? (params.current?.item as Color)?.colorCode
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const isFocused = useNavigationFocus(navigation, {
|
const isFocused = useNavigationFocus(navigation, {
|
||||||
onFocus: (prev) => {
|
onFocus: (prev) => {
|
||||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
|
|
||||||
if (focusControl) return !prev.current;
|
if (focusControl) return !prev.current;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -143,10 +107,7 @@ const NotesPage = ({
|
|||||||
SearchService.update({
|
SearchService.update({
|
||||||
placeholder: `Search in ${item.title}`,
|
placeholder: `Search in ${item.title}`,
|
||||||
type: "notes",
|
type: "notes",
|
||||||
title:
|
title: item.type === "tag" ? "#" + item.title : item.title,
|
||||||
item.type === "tag"
|
|
||||||
? "#" + item.title
|
|
||||||
: toCamelCase((item as Color).title),
|
|
||||||
get: () => {
|
get: () => {
|
||||||
return get(params.current, false);
|
return get(params.current, false);
|
||||||
}
|
}
|
||||||
@@ -154,31 +115,16 @@ const NotesPage = ({
|
|||||||
}, [get]);
|
}, [get]);
|
||||||
|
|
||||||
const syncWithNavigation = React.useCallback(() => {
|
const syncWithNavigation = React.useCallback(() => {
|
||||||
const { item, title } = params.current;
|
const { item } = params.current;
|
||||||
useNavigationStore.getState().update(
|
|
||||||
{
|
useNavigationStore
|
||||||
name: route.name,
|
.getState()
|
||||||
title:
|
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||||
route.name === "ColoredNotes" ? toCamelCase(title as string) : title,
|
|
||||||
id: item?.id,
|
|
||||||
type: "notes",
|
|
||||||
notebookId: item.type === "topic" ? item.notebookId : undefined,
|
|
||||||
color:
|
|
||||||
item.type === "color" && route.name === "ColoredNotes"
|
|
||||||
? item.title?.toLowerCase()
|
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
params.current.canGoBack,
|
|
||||||
rightButtons && rightButtons(params.current)
|
|
||||||
);
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
|
||||||
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
|
||||||
|
|
||||||
!isMonograph &&
|
!isMonograph &&
|
||||||
setOnFirstSave({
|
setOnFirstSave({
|
||||||
type: getItemType(route.name),
|
type: getItemType(route.name),
|
||||||
id: item.id,
|
id: item.id
|
||||||
notebook: item.type === "topic" ? item.notebookId : undefined
|
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
isMonograph,
|
isMonograph,
|
||||||
@@ -192,9 +138,6 @@ const NotesPage = ({
|
|||||||
async (data?: NotesScreenParams) => {
|
async (data?: NotesScreenParams) => {
|
||||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||||
if (data) params.current = data;
|
if (data) params.current = data;
|
||||||
params.current.title =
|
|
||||||
params.current.title ||
|
|
||||||
(params.current.item as Item & { title: string }).title;
|
|
||||||
const { item } = params.current;
|
const { item } = params.current;
|
||||||
try {
|
try {
|
||||||
if (isNew) setLoadingNotes(true);
|
if (isNew) setLoadingNotes(true);
|
||||||
@@ -204,9 +147,8 @@ const NotesPage = ({
|
|||||||
)) as VirtualizedGrouping<Note>;
|
)) as VirtualizedGrouping<Note>;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
((item.type === "tag" || item.type === "color") &&
|
(item.type === "tag" || item.type === "color") &&
|
||||||
(!notes || notes.ids.length === 0)) ||
|
(!notes || notes.ids.length === 0)
|
||||||
(item.type === "topic" && !notes)
|
|
||||||
) {
|
) {
|
||||||
return Navigation.goBack();
|
return Navigation.goBack();
|
||||||
}
|
}
|
||||||
@@ -243,81 +185,51 @@ const NotesPage = ({
|
|||||||
}, [onRequestUpdate, route.name]);
|
}, [onRequestUpdate, route.name]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout
|
<>
|
||||||
color={
|
<Header
|
||||||
route.name === "ColoredNotes"
|
renderedInRoute={route.name}
|
||||||
? (params.current?.item as Color)?.colorCode
|
title={title}
|
||||||
: undefined
|
canGoBack={params?.current?.canGoBack}
|
||||||
}
|
hasSearch={true}
|
||||||
wait={loading || loadingNotes}
|
id={
|
||||||
>
|
route.name === "Monographs" ? "Monographs" : params?.current.item?.id
|
||||||
{/* {route.name === "TopicNotes" ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph
|
|
||||||
onPress={() => {
|
|
||||||
Navigation.navigate({
|
|
||||||
name: "Notebooks",
|
|
||||||
title: "Notebooks"
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
>
|
|
||||||
Notebooks
|
|
||||||
</Paragraph>
|
|
||||||
{notebook ? (
|
|
||||||
<>
|
|
||||||
<IconButton
|
|
||||||
name="chevron-right"
|
|
||||||
size={14}
|
|
||||||
customStyle={{ width: 25, height: 25 }}
|
|
||||||
/>
|
|
||||||
<Paragraph
|
|
||||||
onPress={() => {
|
|
||||||
NotebookScreen.navigate(notebook, true);
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
>
|
|
||||||
{notebook.title}
|
|
||||||
</Paragraph>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
) : null} */}
|
|
||||||
<List
|
|
||||||
data={notes}
|
|
||||||
dataType="note"
|
|
||||||
onRefresh={onRequestUpdate}
|
|
||||||
loading={loading || !isFocused}
|
|
||||||
renderedInRoute="Notes"
|
|
||||||
headerTitle={params.current.title}
|
|
||||||
customAccentColor={
|
|
||||||
route.name === "ColoredNotes"
|
|
||||||
? (params.current?.item as Color)?.colorCode
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
placeholder={placeholder}
|
onSearch={() => {
|
||||||
|
Navigation.push("Search", {
|
||||||
|
placeholder: `Type a keyword to search in ${title}`,
|
||||||
|
type: "note",
|
||||||
|
title: title,
|
||||||
|
route: route.name,
|
||||||
|
ids: notes?.ids?.filter((id) => typeof id === "string") as string[]
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
accentColor={accentColor}
|
||||||
|
onPressDefaultRightButton={onPressFloatingButton}
|
||||||
|
headerRightButtons={rightButtons?.(params?.current)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isMonograph &&
|
<DelayLayout color={accentColor} wait={loading || loadingNotes}>
|
||||||
((notes?.ids && (notes?.ids?.length || 0) > 0) || isFocused) ? (
|
<List
|
||||||
<FloatingButton
|
data={notes}
|
||||||
color={
|
dataType="note"
|
||||||
route.name === "ColoredNotes"
|
onRefresh={onRequestUpdate}
|
||||||
? (params.current?.item as Color)?.colorCode
|
loading={loading || !isFocused}
|
||||||
: undefined
|
renderedInRoute="Notes"
|
||||||
}
|
headerTitle={title}
|
||||||
title="Create a note"
|
customAccentColor={accentColor}
|
||||||
onPress={onPressFloatingButton}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</DelayLayout>
|
{!isMonograph &&
|
||||||
|
((notes?.ids && (notes?.ids?.length || 0) > 0) || isFocused) ? (
|
||||||
|
<FloatingButton
|
||||||
|
color={accentColor}
|
||||||
|
title="Create a note"
|
||||||
|
onPress={onPressFloatingButton}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||||
import { MonographType } from "../../utils/types";
|
|
||||||
import { openMonographsWebpage } from "./common";
|
import { openMonographsWebpage } from "./common";
|
||||||
|
|
||||||
|
export const MONOGRAPH_PLACEHOLDER_DATA = {
|
||||||
|
title: "Your monographs",
|
||||||
|
paragraph: "You have not published any notes as monographs yet.",
|
||||||
|
button: "Learn more about monographs",
|
||||||
|
action: openMonographsWebpage,
|
||||||
|
loading: "Loading published notes.",
|
||||||
|
type: "monograph",
|
||||||
|
buttonIcon: "information-outline"
|
||||||
|
};
|
||||||
|
|
||||||
export const Monographs = ({
|
export const Monographs = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -33,7 +43,7 @@ export const Monographs = ({
|
|||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
route={route}
|
route={route}
|
||||||
get={Monographs.get}
|
get={Monographs.get}
|
||||||
placeholder={PLACEHOLDER_DATA}
|
placeholder={MONOGRAPH_PLACEHOLDER_DATA}
|
||||||
onPressFloatingButton={openMonographsWebpage}
|
onPressFloatingButton={openMonographsWebpage}
|
||||||
canGoBack={route.params?.canGoBack}
|
canGoBack={route.params?.canGoBack}
|
||||||
focusControl={true}
|
focusControl={true}
|
||||||
@@ -49,16 +59,10 @@ Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
|
|||||||
return await db.monographs.all.grouped(db.settings.getGroupOptions("notes"));
|
return await db.monographs.all.grouped(db.settings.getGroupOptions("notes"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Monographs.navigate = (item?: MonographType, canGoBack?: boolean) => {
|
Monographs.navigate = (canGoBack?: boolean) => {
|
||||||
Navigation.navigate<"Monographs">(
|
Navigation.navigate<"Monographs">("Monographs", {
|
||||||
{
|
item: { type: "monograph" } as any,
|
||||||
name: "Monographs",
|
canGoBack: canGoBack as boolean,
|
||||||
type: "monograph"
|
title: "Monographs"
|
||||||
},
|
});
|
||||||
{
|
|
||||||
item: { type: "monograph" } as any,
|
|
||||||
canGoBack: canGoBack as boolean,
|
|
||||||
title: "Monographs"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { Tag } from "@notesnook/core/dist/types";
|
import { Tag } from "@notesnook/core/dist/types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||||
import { openEditor } from "./common";
|
import { PLACEHOLDER_DATA, openEditor } from "./common";
|
||||||
|
|
||||||
export const TaggedNotes = ({
|
export const TaggedNotes = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -53,17 +54,9 @@ TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
|||||||
|
|
||||||
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Navigation.navigate<"TaggedNotes">(
|
Navigation.navigate<"TaggedNotes">("TaggedNotes", {
|
||||||
{
|
item: item,
|
||||||
name: "TaggedNotes",
|
canGoBack,
|
||||||
title: item.title,
|
title: item.title
|
||||||
id: item.id,
|
});
|
||||||
type: "tag"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
item: item,
|
|
||||||
canGoBack,
|
|
||||||
title: item.title
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,104 +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 { Topic } from "@notesnook/core/dist/types";
|
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import React from "react";
|
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
|
||||||
import { db } from "../../common/database";
|
|
||||||
import { MoveNotes } from "../../components/sheets/move-notes/movenote";
|
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
|
||||||
|
|
||||||
import { openEditor } from "./common";
|
|
||||||
|
|
||||||
const headerRightButtons = (params: NotesScreenParams) => [
|
|
||||||
{
|
|
||||||
title: "Edit topic",
|
|
||||||
onPress: () => {
|
|
||||||
const { item } = params;
|
|
||||||
if (item.type !== "topic") return;
|
|
||||||
// eSendEvent(eOpenAddTopicDialog, {
|
|
||||||
// notebookId: item.notebookId,
|
|
||||||
// toEdit: item
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Move notes",
|
|
||||||
onPress: () => {
|
|
||||||
const { item } = params;
|
|
||||||
if (item?.type !== "topic") return;
|
|
||||||
const notebook = db.notebooks?.notebook(item.notebookId);
|
|
||||||
if (notebook) {
|
|
||||||
MoveNotes.present(notebook.data, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const TopicNotes = ({
|
|
||||||
navigation,
|
|
||||||
route
|
|
||||||
}: NavigationProps<"TopicNotes">) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NotesPage
|
|
||||||
navigation={navigation}
|
|
||||||
route={route}
|
|
||||||
get={TopicNotes.get}
|
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
|
||||||
onPressFloatingButton={openEditor}
|
|
||||||
rightButtons={headerRightButtons}
|
|
||||||
canGoBack={route.params?.canGoBack}
|
|
||||||
focusControl={true}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
TopicNotes.get = (params: NotesScreenParams, grouped = true) => {
|
|
||||||
const { id, notebookId } = params.item as Topic;
|
|
||||||
const topic = db.notebooks?.notebook(notebookId)?.topics.topic(id);
|
|
||||||
if (!topic) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const notes = topic?.all || [];
|
|
||||||
return grouped
|
|
||||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
|
||||||
: notes;
|
|
||||||
};
|
|
||||||
|
|
||||||
TopicNotes.navigate = (item: Topic, canGoBack: boolean) => {
|
|
||||||
if (!item) return;
|
|
||||||
Navigation.navigate<"TopicNotes">(
|
|
||||||
{
|
|
||||||
name: "TopicNotes",
|
|
||||||
title: item.title,
|
|
||||||
id: item.id,
|
|
||||||
type: "topic",
|
|
||||||
notebookId: item.notebookId
|
|
||||||
},
|
|
||||||
{
|
|
||||||
item: item,
|
|
||||||
canGoBack,
|
|
||||||
title: item.title
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -18,37 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import { FloatingButton } from "../../components/container/floating-button";
|
import { FloatingButton } from "../../components/container/floating-button";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import ReminderSheet from "../../components/sheets/reminder";
|
import ReminderSheet from "../../components/sheets/reminder";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useReminderStore } from "../../stores/use-reminder-store";
|
import { useReminderStore } from "../../stores/use-reminder-store";
|
||||||
|
|
||||||
const prepareSearch = () => {
|
|
||||||
SearchService.update({
|
|
||||||
placeholder: "Search in reminders",
|
|
||||||
type: "reminders",
|
|
||||||
title: "Reminders",
|
|
||||||
get: () => db.reminders?.all
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
title: "Your reminders",
|
|
||||||
paragraph: "You have not set any reminders yet.",
|
|
||||||
button: "Set a new reminder",
|
|
||||||
action: () => {
|
|
||||||
ReminderSheet.present();
|
|
||||||
},
|
|
||||||
loading: "Loading reminders"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Reminders = ({
|
export const Reminders = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -60,13 +40,8 @@ export const Reminders = ({
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
|
||||||
name: route.name,
|
|
||||||
beta: true
|
|
||||||
});
|
|
||||||
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||||
useNavigationStore.getState().setButtonAction(PLACEHOLDER_DATA.action);
|
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
onBlur: () => false,
|
onBlur: () => false,
|
||||||
@@ -74,23 +49,52 @@ export const Reminders = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={reminders}
|
renderedInRoute={route.name}
|
||||||
dataType="reminder"
|
title={route.name}
|
||||||
headerTitle="Reminders"
|
canGoBack={false}
|
||||||
renderedInRoute="Reminders"
|
hasSearch={true}
|
||||||
loading={!isFocused}
|
onSearch={() => {
|
||||||
placeholder={PLACEHOLDER_DATA}
|
Navigation.push("Search", {
|
||||||
/>
|
placeholder: `Type a keyword to search in ${route.name}`,
|
||||||
|
type: "reminder",
|
||||||
<FloatingButton
|
title: route.name,
|
||||||
title="Set a new reminder"
|
route: route.name
|
||||||
onPress={() => {
|
});
|
||||||
|
}}
|
||||||
|
id={route.name}
|
||||||
|
onPressDefaultRightButton={() => {
|
||||||
ReminderSheet.present();
|
ReminderSheet.present();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
|
||||||
|
<DelayLayout>
|
||||||
|
<List
|
||||||
|
data={reminders}
|
||||||
|
dataType="reminder"
|
||||||
|
headerTitle="Reminders"
|
||||||
|
renderedInRoute="Reminders"
|
||||||
|
loading={!isFocused}
|
||||||
|
placeholder={{
|
||||||
|
title: "Your reminders",
|
||||||
|
paragraph: "You have not set any reminders yet.",
|
||||||
|
button: "Set a new reminder",
|
||||||
|
action: () => {
|
||||||
|
ReminderSheet.present();
|
||||||
|
},
|
||||||
|
loading: "Loading reminders"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FloatingButton
|
||||||
|
title="Set a new reminder"
|
||||||
|
onPress={() => {
|
||||||
|
ReminderSheet.present();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +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 React, { useEffect } from "react";
|
|
||||||
import DelayLayout from "../../components/delay-layout";
|
|
||||||
import List from "../../components/list";
|
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
|
||||||
import { useSearchStore } from "../../stores/use-search-store";
|
|
||||||
import { inputRef } from "../../utils/global-refs";
|
|
||||||
import { sleep } from "../../utils/time";
|
|
||||||
export const Search = ({ navigation, route }) => {
|
|
||||||
const searchResults = useSearchStore((state) => state.searchResults);
|
|
||||||
const searching = useSearchStore((state) => state.searching);
|
|
||||||
const searchStatus = useSearchStore((state) => state.searchStatus);
|
|
||||||
const setSearchResults = useSearchStore((state) => state.setSearchResults);
|
|
||||||
const setSearchStatus = useSearchStore((state) => state.setSearchStatus);
|
|
||||||
|
|
||||||
useNavigationFocus(navigation, {
|
|
||||||
onFocus: () => {
|
|
||||||
sleep(300).then(() => inputRef.current?.focus());
|
|
||||||
useNavigationStore.getState().update({
|
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
onBlur: () => false
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
setSearchResults([]);
|
|
||||||
setSearchStatus(false, null);
|
|
||||||
};
|
|
||||||
}, [setSearchResults, setSearchStatus]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DelayLayout wait={searching}>
|
|
||||||
<List
|
|
||||||
listData={searchResults}
|
|
||||||
type="search"
|
|
||||||
screen="Search"
|
|
||||||
focused={() => navigation.isFocused()}
|
|
||||||
placeholderText={"Notes you write appear here"}
|
|
||||||
jumpToDialog={true}
|
|
||||||
loading={searching}
|
|
||||||
CustomHeader={true}
|
|
||||||
placeholderData={{
|
|
||||||
heading: "Search",
|
|
||||||
paragraph:
|
|
||||||
searchStatus ||
|
|
||||||
`Type a keyword to search in ${
|
|
||||||
SearchService.getSearchInformation().title
|
|
||||||
}`,
|
|
||||||
button: null,
|
|
||||||
loading: "Searching..."
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DelayLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
84
apps/mobile/app/screens/search/index.tsx
Normal file
84
apps/mobile/app/screens/search/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
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, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import List from "../../components/list";
|
||||||
|
import { NavigationProps } from "../../services/navigation";
|
||||||
|
import { SearchBar } from "./search-bar";
|
||||||
|
import { db } from "../../common/database";
|
||||||
|
export const Search = ({ route }: NavigationProps<"Search">) => {
|
||||||
|
const [results, setResults] = useState<VirtualizedGrouping<Item>>();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searchStatus, setSearchStatus] = useState<string>();
|
||||||
|
|
||||||
|
const onSearch = async (query: string) => {
|
||||||
|
if (!query) {
|
||||||
|
setResults(undefined);
|
||||||
|
setLoading(false);
|
||||||
|
setSearchStatus(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const type =
|
||||||
|
route.params.type === "trash"
|
||||||
|
? "trash"
|
||||||
|
: ((route.params?.type + "s") as keyof typeof db.lookup);
|
||||||
|
console.log(
|
||||||
|
`Searching in ${type} for ${query}`,
|
||||||
|
route.params?.ids?.length
|
||||||
|
);
|
||||||
|
const results = await db.lookup[type](
|
||||||
|
query,
|
||||||
|
route.params?.type === "note" ? route.params?.ids : undefined
|
||||||
|
);
|
||||||
|
console.log(`Found ${results.ids?.length} results for ${query}`);
|
||||||
|
setResults(results);
|
||||||
|
if (results.ids?.length === 0) {
|
||||||
|
setSearchStatus(`No results found for ${query}`);
|
||||||
|
} else {
|
||||||
|
setSearchStatus(undefined);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SearchBar onChangeText={onSearch} loading={loading} />
|
||||||
|
<List
|
||||||
|
data={results}
|
||||||
|
dataType={route.params?.type}
|
||||||
|
renderedInRoute={route.name}
|
||||||
|
loading={false}
|
||||||
|
placeholder={{
|
||||||
|
title: route.name,
|
||||||
|
paragraph:
|
||||||
|
searchStatus ||
|
||||||
|
`Type a keyword to search in ${route.params?.title}`,
|
||||||
|
loading: "Searching..."
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,148 +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 React, { useEffect, useRef, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { TextInput } from "react-native-gesture-handler";
|
|
||||||
import { IconButton } from "../../components/ui/icon-button";
|
|
||||||
import { ToastManager } from "../../services/event-manager";
|
|
||||||
import Navigation from "../../services/navigation";
|
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import { useSearchStore } from "../../stores/use-search-store";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { SIZE } from "../../utils/size";
|
|
||||||
import { sleep } from "../../utils/time";
|
|
||||||
export const SearchBar = () => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
const [value, setValue] = useState(null);
|
|
||||||
const inputRef = useRef();
|
|
||||||
const setSearchResults = useSearchStore((state) => state.setSearchResults);
|
|
||||||
const setSearchStatus = useSearchStore((state) => state.setSearchStatus);
|
|
||||||
const searchingRef = useRef(0);
|
|
||||||
const onClear = () => {
|
|
||||||
//inputRef.current?.blur();
|
|
||||||
inputRef.current?.clear();
|
|
||||||
setValue(0);
|
|
||||||
SearchService.setTerm(null);
|
|
||||||
setSearchResults([]);
|
|
||||||
setSearchStatus(false, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
sleep(300).then(() => {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onChangeText = (value) => {
|
|
||||||
setValue(value);
|
|
||||||
search(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const search = (value) => {
|
|
||||||
clearTimeout(searchingRef.current);
|
|
||||||
searchingRef.current = setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
if (value === "" || !value) {
|
|
||||||
setSearchResults([]);
|
|
||||||
setSearchStatus(false, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value?.length > 0) {
|
|
||||||
SearchService.setTerm(value);
|
|
||||||
await SearchService.search();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Error occured while searching",
|
|
||||||
message: e.message,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 50,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
flexShrink: 1,
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
name="arrow-left"
|
|
||||||
size={SIZE.xl}
|
|
||||||
top={10}
|
|
||||||
bottom={10}
|
|
||||||
onPress={() => {
|
|
||||||
SearchService.setTerm(null);
|
|
||||||
Navigation.goBack();
|
|
||||||
}}
|
|
||||||
color={colors.primary.paragraph}
|
|
||||||
type="gray"
|
|
||||||
customStyle={{
|
|
||||||
paddingLeft: 0,
|
|
||||||
marginLeft: 0,
|
|
||||||
marginRight: 5
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
ref={inputRef}
|
|
||||||
testID="search-input"
|
|
||||||
style={{
|
|
||||||
fontSize: SIZE.md + 1,
|
|
||||||
fontFamily: "OpenSans-Regular",
|
|
||||||
flexGrow: 1,
|
|
||||||
height: "100%",
|
|
||||||
color: colors.primary.paragraph
|
|
||||||
}}
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
placeholder="Type a keyword"
|
|
||||||
textContentType="none"
|
|
||||||
returnKeyLabel="Search"
|
|
||||||
returnKeyType="search"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
placeholderTextColor={colors.primary.placeholder}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{value && value.length > 0 ? (
|
|
||||||
<IconButton
|
|
||||||
name="close"
|
|
||||||
size={SIZE.md + 2}
|
|
||||||
top={20}
|
|
||||||
bottom={20}
|
|
||||||
right={20}
|
|
||||||
onPress={onClear}
|
|
||||||
type="grayBg"
|
|
||||||
color={colors.primary.icon}
|
|
||||||
customStyle={{
|
|
||||||
width: 25,
|
|
||||||
height: 25
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
94
apps/mobile/app/screens/search/search-bar.tsx
Normal file
94
apps/mobile/app/screens/search/search-bar.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
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, { useRef } from "react";
|
||||||
|
import { 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";
|
||||||
|
export const SearchBar = ({
|
||||||
|
onChangeText,
|
||||||
|
loading
|
||||||
|
}: {
|
||||||
|
onChangeText: (value: string) => void;
|
||||||
|
loading?: boolean;
|
||||||
|
}) => {
|
||||||
|
const insets = useGlobalSafeAreaInsets();
|
||||||
|
const { colors } = useThemeColors();
|
||||||
|
const inputRef = useRef<TextInput>(null);
|
||||||
|
const _onChangeText = (value: string) => {
|
||||||
|
onChangeText(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
height: 50 + insets.top,
|
||||||
|
paddingTop: insets.top,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
flexShrink: 1,
|
||||||
|
width: "100%",
|
||||||
|
paddingHorizontal: 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
name="arrow-left"
|
||||||
|
size={SIZE.xxl}
|
||||||
|
top={10}
|
||||||
|
bottom={10}
|
||||||
|
onPress={() => {
|
||||||
|
Navigation.goBack();
|
||||||
|
}}
|
||||||
|
color={colors.primary.paragraph}
|
||||||
|
type="gray"
|
||||||
|
customStyle={{
|
||||||
|
paddingLeft: 0,
|
||||||
|
marginLeft: -5,
|
||||||
|
marginRight: DDS.isLargeTablet() ? 10 : 7
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
ref={inputRef}
|
||||||
|
testID="search-input"
|
||||||
|
style={{
|
||||||
|
fontSize: SIZE.md + 1,
|
||||||
|
fontFamily: "OpenSans-Regular",
|
||||||
|
flexGrow: 1,
|
||||||
|
height: "100%",
|
||||||
|
color: colors.primary.paragraph
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
onChangeText={_onChangeText}
|
||||||
|
placeholder="Type a keyword"
|
||||||
|
textContentType="none"
|
||||||
|
returnKeyLabel="Search"
|
||||||
|
returnKeyType="search"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
placeholderTextColor={colors.primary.placeholder}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -71,7 +71,7 @@ export const useDragState = create<DragState>(
|
|||||||
|
|
||||||
presets["custom"] = _data;
|
presets["custom"] = _data;
|
||||||
db.settings.setToolbarConfig(
|
db.settings.setToolbarConfig(
|
||||||
useSettingStore.getState().deviceMode || "mobile",
|
useSettingStore.getState().deviceMode || ("mobile" as any),
|
||||||
{
|
{
|
||||||
preset: "custom",
|
preset: "custom",
|
||||||
config: clone(_data)
|
config: clone(_data)
|
||||||
@@ -81,7 +81,7 @@ export const useDragState = create<DragState>(
|
|||||||
},
|
},
|
||||||
setPreset: (preset) => {
|
setPreset: (preset) => {
|
||||||
db.settings.setToolbarConfig(
|
db.settings.setToolbarConfig(
|
||||||
useSettingStore.getState().deviceMode || "mobile",
|
useSettingStore.getState().deviceMode || ("mobile" as any),
|
||||||
{
|
{
|
||||||
preset,
|
preset,
|
||||||
config: preset === "custom" ? clone(get().customPresetData) : []
|
config: preset === "custom" ? clone(get().customPresetData) : []
|
||||||
@@ -99,7 +99,7 @@ export const useDragState = create<DragState>(
|
|||||||
const user = await db.user?.getUser();
|
const user = await db.user?.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
const toolbarConfig = db.settings.getToolbarConfig(
|
const toolbarConfig = db.settings.getToolbarConfig(
|
||||||
useSettingStore.getState().deviceMode || "mobile"
|
useSettingStore.getState().deviceMode || ("mobile" as any)
|
||||||
);
|
);
|
||||||
if (!toolbarConfig) {
|
if (!toolbarConfig) {
|
||||||
logger.info("DragState", "No user defined toolbar config was found");
|
logger.info("DragState", "No user defined toolbar config was found");
|
||||||
@@ -110,25 +110,20 @@ export const useDragState = create<DragState>(
|
|||||||
preset: preset,
|
preset: preset,
|
||||||
data:
|
data:
|
||||||
preset === "custom"
|
preset === "custom"
|
||||||
? clone(toolbarConfig?.config)
|
? clone(toolbarConfig?.config as any[])
|
||||||
: clone(presets[preset]),
|
: clone(presets[preset]),
|
||||||
customPresetData:
|
customPresetData:
|
||||||
preset === "custom"
|
preset === "custom"
|
||||||
? clone(toolbarConfig?.config)
|
? clone(toolbarConfig?.config as any[])
|
||||||
: clone(presets["custom"])
|
: clone(presets["custom"])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "drag-state-storage", // unique name
|
name: "drag-state-storage", // unique name
|
||||||
getStorage: () => MMKV as StateStorage,
|
getStorage: () => MMKV as unknown as StateStorage,
|
||||||
onRehydrateStorage: () => {
|
onRehydrateStorage: () => {
|
||||||
return () => {
|
return () => {
|
||||||
logger.info(
|
|
||||||
"DragState",
|
|
||||||
"rehydrated drag state",
|
|
||||||
useNoteStore.getState().loading
|
|
||||||
);
|
|
||||||
if (!useNoteStore.getState().loading) {
|
if (!useNoteStore.getState().loading) {
|
||||||
useDragState.getState().init();
|
useDragState.getState().init();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { tabBarRef } from "../../utils/global-refs";
|
|||||||
import { components } from "./components";
|
import { components } from "./components";
|
||||||
import { SectionItem } from "./section-item";
|
import { SectionItem } from "./section-item";
|
||||||
import { RouteParams, SettingSection } from "./types";
|
import { RouteParams, SettingSection } from "./types";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
|
|
||||||
const keyExtractor = (item: SettingSection) => item.id;
|
const keyExtractor = (item: SettingSection) => item.id;
|
||||||
const AnimatedKeyboardAvoidingFlatList = Animated.createAnimatedComponent(
|
const AnimatedKeyboardAvoidingFlatList = Animated.createAnimatedComponent(
|
||||||
@@ -42,13 +43,7 @@ const Group = ({
|
|||||||
useNavigationFocus(navigation, {
|
useNavigationFocus(navigation, {
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
tabBarRef.current?.lock();
|
tabBarRef.current?.lock();
|
||||||
useNavigationStore.getState().update(
|
useNavigationStore.getState().setFocusedRouteId("Settings");
|
||||||
{
|
|
||||||
name: "SettingsGroup",
|
|
||||||
title: route.params.name as string
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -62,25 +57,33 @@ const Group = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout type="settings" delay={1}>
|
<>
|
||||||
<View
|
<Header
|
||||||
style={{
|
renderedInRoute="Settings"
|
||||||
flex: 1
|
title={route.params.name as string}
|
||||||
}}
|
canGoBack={true}
|
||||||
>
|
id="Settings"
|
||||||
{route.params.sections ? (
|
/>
|
||||||
<AnimatedKeyboardAvoidingFlatList
|
<DelayLayout type="settings" delay={1}>
|
||||||
entering={FadeInDown}
|
<View
|
||||||
data={route.params.sections}
|
style={{
|
||||||
keyExtractor={keyExtractor}
|
flex: 1
|
||||||
renderItem={renderItem}
|
}}
|
||||||
enableOnAndroid
|
>
|
||||||
enableAutomaticScroll
|
{route.params.sections ? (
|
||||||
/>
|
<AnimatedKeyboardAvoidingFlatList
|
||||||
) : null}
|
entering={FadeInDown}
|
||||||
{route.params.component ? components[route.params.component] : null}
|
data={route.params.sections}
|
||||||
</View>
|
keyExtractor={keyExtractor}
|
||||||
</DelayLayout>
|
renderItem={renderItem}
|
||||||
|
enableOnAndroid
|
||||||
|
enableAutomaticScroll
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{route.params.component ? components[route.params.component] : null}
|
||||||
|
</View>
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { SectionGroup } from "./section-group";
|
|||||||
import { settingsGroups } from "./settings-data";
|
import { settingsGroups } from "./settings-data";
|
||||||
import { RouteParams, SettingSection } from "./types";
|
import { RouteParams, SettingSection } from "./types";
|
||||||
import SettingsUserSection from "./user-section";
|
import SettingsUserSection from "./user-section";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
const keyExtractor = (item: SettingSection) => item.id;
|
const keyExtractor = (item: SettingSection) => item.id;
|
||||||
|
|
||||||
const Home = ({
|
const Home = ({
|
||||||
@@ -48,9 +49,7 @@ const Home = ({
|
|||||||
|
|
||||||
useNavigationFocus(navigation, {
|
useNavigationFocus(navigation, {
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId("Settings");
|
||||||
name: "Settings"
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
focusOnInit: true
|
focusOnInit: true
|
||||||
@@ -74,19 +73,17 @@ const Home = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout delay={300} type="settings">
|
<>
|
||||||
{loading && (
|
<Header
|
||||||
//@ts-ignore // Migrate to typescript required.
|
renderedInRoute="Settings"
|
||||||
<BaseDialog animated={false} bounce={false} visible={true}>
|
title="Settings"
|
||||||
<View
|
canGoBack={false}
|
||||||
style={{
|
id="Settings"
|
||||||
width: "100%",
|
/>
|
||||||
height: "100%",
|
<DelayLayout delay={300} type="settings">
|
||||||
backgroundColor: colors.primary.background,
|
{loading && (
|
||||||
justifyContent: "center",
|
//@ts-ignore // Migrate to typescript required.
|
||||||
alignItems: "center"
|
<BaseDialog animated={false} bounce={false} visible={true}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -96,45 +93,55 @@ const Home = ({
|
|||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading color={colors.primary.paragraph} size={SIZE.lg}>
|
|
||||||
Logging out
|
|
||||||
</Heading>
|
|
||||||
<Paragraph color={colors.secondary.paragraph}>
|
|
||||||
Please wait while we log out and clear app data.
|
|
||||||
</Paragraph>
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
width: "100%",
|
||||||
width: 100,
|
height: "100%",
|
||||||
marginTop: 15
|
backgroundColor: colors.primary.background,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProgressBarComponent
|
<Heading color={colors.primary.paragraph} size={SIZE.lg}>
|
||||||
height={5}
|
Logging out
|
||||||
width={100}
|
</Heading>
|
||||||
animated={true}
|
<Paragraph color={colors.secondary.paragraph}>
|
||||||
useNativeDriver
|
Please wait while we log out and clear app data.
|
||||||
indeterminate
|
</Paragraph>
|
||||||
indeterminateAnimationDuration={2000}
|
<View
|
||||||
unfilledColor={colors.secondary.background}
|
style={{
|
||||||
color={colors.primary.accent}
|
flexDirection: "row",
|
||||||
borderWidth={0}
|
width: 100,
|
||||||
/>
|
marginTop: 15
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProgressBarComponent
|
||||||
|
height={5}
|
||||||
|
width={100}
|
||||||
|
animated={true}
|
||||||
|
useNativeDriver
|
||||||
|
indeterminate
|
||||||
|
indeterminateAnimationDuration={2000}
|
||||||
|
unfilledColor={colors.secondary.background}
|
||||||
|
color={colors.primary.accent}
|
||||||
|
borderWidth={0}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</BaseDialog>
|
||||||
</BaseDialog>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<Animated.FlatList
|
<Animated.FlatList
|
||||||
entering={FadeInDown}
|
entering={FadeInDown}
|
||||||
data={settingsGroups}
|
data={settingsGroups}
|
||||||
windowSize={1}
|
windowSize={1}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
ListFooterComponent={<View style={{ height: 200 }} />}
|
ListFooterComponent={<View style={{ height: 200 }} />}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,34 +26,6 @@ import Home from "./home";
|
|||||||
import { RouteParams } from "./types";
|
import { RouteParams } from "./types";
|
||||||
const SettingsStack = createNativeStackNavigator<RouteParams>();
|
const SettingsStack = createNativeStackNavigator<RouteParams>();
|
||||||
|
|
||||||
// const Home = React.lazy(() => import(/* webpackChunkName: "settings-home" */ './home'));
|
|
||||||
// const Group = React.lazy(() => import(/* webpackChunkName: "settings-group" */ './group'));
|
|
||||||
|
|
||||||
// const Fallback = () => {
|
|
||||||
// return (
|
|
||||||
// <>
|
|
||||||
// <Header />
|
|
||||||
// <DelayLayout wait={true} type="settings" />
|
|
||||||
// </>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const HomeScreen = (props: NativeStackScreenProps<RouteParams, 'SettingsHome'>) => {
|
|
||||||
// return (
|
|
||||||
// <React.Suspense fallback={<Fallback />}>
|
|
||||||
// <Home {...props} />
|
|
||||||
// </React.Suspense>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const GroupScreen = (props: NativeStackScreenProps<RouteParams, 'SettingsGroup'>) => {
|
|
||||||
// return (
|
|
||||||
// <React.Suspense fallback={<Fallback />}>
|
|
||||||
// <Group {...props} />
|
|
||||||
// </React.Suspense>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
return (
|
return (
|
||||||
@@ -62,7 +34,7 @@ export const Settings = () => {
|
|||||||
screenListeners={{
|
screenListeners={{
|
||||||
focus: (e) => {
|
focus: (e) => {
|
||||||
if (e.target?.startsWith("SettingsHome-")) {
|
if (e.target?.startsWith("SettingsHome-")) {
|
||||||
useNavigationStore.getState().update({ name: "Settings" }, false);
|
useNavigationStore.getState().update("Settings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -18,23 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { db } from "../../common/database";
|
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useTagStore } from "../../stores/use-tag-store";
|
import { useTagStore } from "../../stores/use-tag-store";
|
||||||
const prepareSearch = () => {
|
import { db } from "../../common/database";
|
||||||
SearchService.update({
|
|
||||||
placeholder: "Search in tags",
|
|
||||||
type: "tags",
|
|
||||||
title: "Tags",
|
|
||||||
get: () => db.tags?.all
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
||||||
const tags = useTagStore((state) => state.tags);
|
const tags = useTagStore((state) => state.tags);
|
||||||
@@ -44,11 +36,7 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
|
|
||||||
SearchService.prepareSearch = prepareSearch;
|
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
onBlur: () => false,
|
onBlur: () => false,
|
||||||
@@ -56,20 +44,36 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={tags}
|
renderedInRoute={route.name}
|
||||||
dataType="tag"
|
title={route.name}
|
||||||
headerTitle="Tags"
|
canGoBack={false}
|
||||||
loading={!isFocused}
|
hasSearch={true}
|
||||||
renderedInRoute="Tags"
|
onSearch={() => {
|
||||||
placeholder={{
|
Navigation.push("Search", {
|
||||||
title: "Your tags",
|
placeholder: `Type a keyword to search in ${route.name}`,
|
||||||
paragraph: "You have not created any tags for your notes yet.",
|
type: "tag",
|
||||||
loading: "Loading your tags."
|
title: route.name,
|
||||||
|
route: route.name
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
<DelayLayout>
|
||||||
|
<List
|
||||||
|
data={tags}
|
||||||
|
dataType="tag"
|
||||||
|
headerTitle="Tags"
|
||||||
|
loading={!isFocused}
|
||||||
|
renderedInRoute="Tags"
|
||||||
|
placeholder={{
|
||||||
|
title: "Your tags",
|
||||||
|
paragraph: "You have not created any tags for your notes yet.",
|
||||||
|
loading: "Loading your tags."
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,22 +22,14 @@ import { db } from "../../common/database";
|
|||||||
import { FloatingButton } from "../../components/container/floating-button";
|
import { FloatingButton } from "../../components/container/floating-button";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
import { presentDialog } from "../../components/dialog/functions";
|
import { presentDialog } from "../../components/dialog/functions";
|
||||||
|
import { Header } from "../../components/header";
|
||||||
import List from "../../components/list";
|
import List from "../../components/list";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import { ToastManager } from "../../services/event-manager";
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import SearchService from "../../services/search";
|
|
||||||
import useNavigationStore from "../../stores/use-navigation-store";
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useTrashStore } from "../../stores/use-trash-store";
|
import { useTrashStore } from "../../stores/use-trash-store";
|
||||||
const prepareSearch = () => {
|
|
||||||
SearchService.update({
|
|
||||||
placeholder: "Search in trash",
|
|
||||||
type: "trash",
|
|
||||||
title: "Trash",
|
|
||||||
get: () => db.trash?.all
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPressFloatingButton = () => {
|
const onPressFloatingButton = () => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
@@ -81,40 +73,53 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
|
|||||||
route.name,
|
route.name,
|
||||||
Navigation.routeUpdateFunctions[route.name]
|
Navigation.routeUpdateFunctions[route.name]
|
||||||
);
|
);
|
||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||||
name: route.name
|
|
||||||
});
|
|
||||||
if (
|
if (
|
||||||
!useTrashStore.getState().trash ||
|
!useTrashStore.getState().trash ||
|
||||||
useTrashStore.getState().trash?.ids?.length === 0
|
useTrashStore.getState().trash?.ids?.length === 0
|
||||||
) {
|
) {
|
||||||
useTrashStore.getState().setTrash();
|
useTrashStore.getState().setTrash();
|
||||||
}
|
}
|
||||||
SearchService.prepareSearch = prepareSearch;
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onBlur: () => false
|
onBlur: () => false
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<>
|
||||||
<List
|
<Header
|
||||||
data={trash}
|
renderedInRoute={route.name}
|
||||||
dataType="trash"
|
title={route.name}
|
||||||
renderedInRoute="Trash"
|
canGoBack={false}
|
||||||
loading={!isFocused}
|
hasSearch={true}
|
||||||
placeholder={PLACEHOLDER_DATA(db.settings.getTrashCleanupInterval())}
|
onSearch={() => {
|
||||||
headerTitle="Trash"
|
Navigation.push("Search", {
|
||||||
|
placeholder: `Type a keyword to search in ${route.name}`,
|
||||||
|
type: "trash",
|
||||||
|
title: route.name,
|
||||||
|
route: route.name
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<DelayLayout>
|
||||||
{trash && trash?.ids?.length !== 0 ? (
|
<List
|
||||||
<FloatingButton
|
data={trash}
|
||||||
title="Clear all trash"
|
dataType="trash"
|
||||||
onPress={onPressFloatingButton}
|
renderedInRoute="Trash"
|
||||||
alwaysVisible={true}
|
loading={!isFocused}
|
||||||
|
placeholder={PLACEHOLDER_DATA(db.settings.getTrashCleanupInterval())}
|
||||||
|
headerTitle="Trash"
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</DelayLayout>
|
{trash && trash?.ids?.length !== 0 ? (
|
||||||
|
<FloatingButton
|
||||||
|
title="Clear all trash"
|
||||||
|
onPress={onPressFloatingButton}
|
||||||
|
alwaysVisible={true}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</DelayLayout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { StackActions } from "@react-navigation/native";
|
|||||||
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
||||||
import { useFavoriteStore } from "../stores/use-favorite-store";
|
import { useFavoriteStore } from "../stores/use-favorite-store";
|
||||||
import useNavigationStore, {
|
import useNavigationStore, {
|
||||||
CurrentScreen,
|
|
||||||
GenericRouteParam,
|
GenericRouteParam,
|
||||||
RouteName,
|
RouteName,
|
||||||
RouteParams
|
RouteParams
|
||||||
@@ -34,8 +33,8 @@ import { useTrashStore } from "../stores/use-trash-store";
|
|||||||
import { eOnNewTopicAdded } from "../utils/events";
|
import { eOnNewTopicAdded } from "../utils/events";
|
||||||
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs";
|
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs";
|
||||||
import { eSendEvent } from "./event-manager";
|
import { eSendEvent } from "./event-manager";
|
||||||
import SettingsService from "./settings";
|
|
||||||
import SearchService from "./search";
|
import SearchService from "./search";
|
||||||
|
import SettingsService from "./settings";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes that should be updated on focus
|
* Routes that should be updated on focus
|
||||||
@@ -113,59 +112,35 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
|
|||||||
routesToUpdate?.length > 0
|
routesToUpdate?.length > 0
|
||||||
? routesToUpdate
|
? routesToUpdate
|
||||||
: (Object.keys(routeNames) as (keyof RouteParams)[]);
|
: (Object.keys(routeNames) as (keyof RouteParams)[]);
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
const currentRoute = useNavigationStore.getState().currentRoute;
|
||||||
if (routes.indexOf(currentScreen.name) > -1) {
|
if (routes.indexOf(currentRoute) > -1) {
|
||||||
routeUpdateFunctions[currentScreen.name]?.();
|
routeUpdateFunctions[currentRoute]?.();
|
||||||
clearRouteFromQueue(currentScreen.name);
|
clearRouteFromQueue(currentRoute);
|
||||||
// Remove focused screen from queue
|
// Remove focused screen from queue
|
||||||
routes.splice(routes.indexOf(currentScreen.name), 1);
|
routes.splice(routes.indexOf(currentRoute), 1);
|
||||||
}
|
}
|
||||||
routesUpdateQueue = routesUpdateQueue.concat(routes);
|
routesUpdateQueue = routesUpdateQueue.concat(routes);
|
||||||
routesUpdateQueue = [...new Set(routesUpdateQueue)];
|
routesUpdateQueue = [...new Set(routesUpdateQueue)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigate<T extends RouteName>(
|
function navigate<T extends RouteName>(screen: T, params?: RouteParams[T]) {
|
||||||
screen: Omit<Partial<CurrentScreen>, "name"> & {
|
rootNavigatorRef.current?.navigate(screen as any, params);
|
||||||
name: keyof RouteParams;
|
|
||||||
},
|
|
||||||
params?: RouteParams[T]
|
|
||||||
) {
|
|
||||||
useNavigationStore
|
|
||||||
.getState()
|
|
||||||
.update(screen as CurrentScreen, !!params?.canGoBack);
|
|
||||||
if (screen.name === "Notebook")
|
|
||||||
routeUpdateFunctions["Notebook"](params || {});
|
|
||||||
if (screen.name?.endsWith("Notes") && screen.name !== "Notes")
|
|
||||||
routeUpdateFunctions[screen.name]?.(params || {});
|
|
||||||
//@ts-ignore Not sure how to fix this for now ignore it.
|
|
||||||
rootNavigatorRef.current?.navigate<RouteName>(screen.name, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
rootNavigatorRef.current?.goBack();
|
rootNavigatorRef.current?.goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
function push<T extends RouteName>(
|
function push<T extends RouteName>(screen: T, params: RouteParams[T]) {
|
||||||
screen: CurrentScreen,
|
rootNavigatorRef.current?.dispatch(StackActions.push(screen as any, params));
|
||||||
params: RouteParams[T]
|
|
||||||
) {
|
|
||||||
useNavigationStore.getState().update(screen, !!params?.canGoBack);
|
|
||||||
rootNavigatorRef.current?.dispatch(StackActions.push(screen.name, params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function replace<T extends RouteName>(
|
function replace<T extends RouteName>(screen: T, params: RouteParams[T]) {
|
||||||
screen: CurrentScreen,
|
rootNavigatorRef.current?.dispatch(StackActions.replace(screen, params));
|
||||||
params: RouteParams[T]
|
|
||||||
) {
|
|
||||||
useNavigationStore.getState().update(screen, !!params?.canGoBack);
|
|
||||||
rootNavigatorRef.current?.dispatch(StackActions.replace(screen.name, params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function popToTop() {
|
function popToTop() {
|
||||||
rootNavigatorRef.current?.dispatch(StackActions.popToTop());
|
rootNavigatorRef.current?.dispatch(StackActions.popToTop());
|
||||||
useNavigationStore.getState().update({
|
|
||||||
name: (SettingsService.get().homepage as RouteName) || "Notes"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDrawer() {
|
function openDrawer() {
|
||||||
|
|||||||
71
apps/mobile/app/stores/item-selection-store.ts
Normal file
71
apps/mobile/app/stores/item-selection-store.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
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 create, { State } from "zustand";
|
||||||
|
|
||||||
|
export type SelectionState = "intermediate" | "selected" | "deselected";
|
||||||
|
export type ItemSelection = Record<string, SelectionState | undefined>;
|
||||||
|
export interface SelectionStore extends State {
|
||||||
|
selection: ItemSelection;
|
||||||
|
setSelection: (state: ItemSelection) => void;
|
||||||
|
multiSelect: boolean;
|
||||||
|
toggleMultiSelect: (multiSelect: boolean) => void;
|
||||||
|
initialState: ItemSelection;
|
||||||
|
canEnableMultiSelectMode: boolean;
|
||||||
|
markAs: (item: Item, state: SelectionState | undefined) => void;
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createItemSelectionStore(multiSelectMode = false) {
|
||||||
|
return create<SelectionStore>((set, get) => ({
|
||||||
|
selection: {},
|
||||||
|
setSelection: (state) => {
|
||||||
|
set({
|
||||||
|
selection: state
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reset: () => {
|
||||||
|
set({
|
||||||
|
selection: { ...get().initialState }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
canEnableMultiSelectMode: multiSelectMode,
|
||||||
|
initialState: {},
|
||||||
|
markAs: (item, state) => {
|
||||||
|
set({
|
||||||
|
selection: {
|
||||||
|
...get().selection,
|
||||||
|
[item.id]:
|
||||||
|
state === "deselected"
|
||||||
|
? get().initialState === undefined
|
||||||
|
? undefined
|
||||||
|
: "deselected"
|
||||||
|
: state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
multiSelect: false,
|
||||||
|
toggleMultiSelect: () => {
|
||||||
|
if (!get().canEnableMultiSelectMode) return;
|
||||||
|
set({
|
||||||
|
multiSelect: !get().multiSelect
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -19,17 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Color,
|
Color,
|
||||||
|
ItemType,
|
||||||
Note,
|
Note,
|
||||||
Notebook,
|
Notebook,
|
||||||
Reminder,
|
Reminder,
|
||||||
Tag,
|
Tag,
|
||||||
Topic,
|
|
||||||
TrashItem
|
TrashItem
|
||||||
} from "@notesnook/core/dist/types";
|
} from "@notesnook/core/dist/types";
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { ColorValues } from "../utils/colors";
|
import { ColorValues } from "../utils/colors";
|
||||||
|
|
||||||
export type GenericRouteParam = { [name: string]: unknown };
|
export type GenericRouteParam = undefined;
|
||||||
|
|
||||||
export type NotebookScreenParams = {
|
export type NotebookScreenParams = {
|
||||||
item: Notebook;
|
item: Notebook;
|
||||||
@@ -38,7 +38,7 @@ export type NotebookScreenParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type NotesScreenParams = {
|
export type NotesScreenParams = {
|
||||||
item: Note | Notebook | Topic | Tag | Color | TrashItem | Reminder;
|
item: Note | Notebook | Tag | Color | TrashItem | Reminder;
|
||||||
title: string;
|
title: string;
|
||||||
canGoBack?: boolean;
|
canGoBack?: boolean;
|
||||||
};
|
};
|
||||||
@@ -56,90 +56,64 @@ export type AuthParams = {
|
|||||||
|
|
||||||
export type RouteParams = {
|
export type RouteParams = {
|
||||||
Notes: GenericRouteParam;
|
Notes: GenericRouteParam;
|
||||||
Notebooks: GenericRouteParam;
|
Notebooks: {
|
||||||
|
canGoBack?: boolean;
|
||||||
|
};
|
||||||
Notebook: NotebookScreenParams;
|
Notebook: NotebookScreenParams;
|
||||||
NotesPage: NotesScreenParams;
|
NotesPage: NotesScreenParams;
|
||||||
Tags: GenericRouteParam;
|
Tags: GenericRouteParam;
|
||||||
Favorites: GenericRouteParam;
|
Favorites: GenericRouteParam;
|
||||||
Trash: GenericRouteParam;
|
Trash: GenericRouteParam;
|
||||||
Search: GenericRouteParam;
|
Search: {
|
||||||
|
placeholder: string;
|
||||||
|
type: ItemType;
|
||||||
|
title: string;
|
||||||
|
route: RouteName;
|
||||||
|
ids?: string[];
|
||||||
|
};
|
||||||
Settings: GenericRouteParam;
|
Settings: GenericRouteParam;
|
||||||
TaggedNotes: NotesScreenParams;
|
TaggedNotes: NotesScreenParams;
|
||||||
ColoredNotes: NotesScreenParams;
|
ColoredNotes: NotesScreenParams;
|
||||||
TopicNotes: NotesScreenParams;
|
TopicNotes: NotesScreenParams;
|
||||||
Monographs: NotesScreenParams;
|
Monographs: NotesScreenParams;
|
||||||
AppLock: AppLockRouteParams;
|
AppLock: AppLockRouteParams;
|
||||||
Auth: AuthParams;
|
|
||||||
Reminders: GenericRouteParam;
|
Reminders: GenericRouteParam;
|
||||||
SettingsGroup: GenericRouteParam;
|
SettingsGroup: GenericRouteParam;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RouteName = keyof RouteParams;
|
export type RouteName = keyof RouteParams;
|
||||||
|
|
||||||
export type CurrentScreen = {
|
|
||||||
name: RouteName;
|
|
||||||
id: string;
|
|
||||||
title?: string;
|
|
||||||
type?: string;
|
|
||||||
color?: string | null;
|
|
||||||
notebookId?: string;
|
|
||||||
beta?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HeaderRightButton = {
|
export type HeaderRightButton = {
|
||||||
title: string;
|
title: string;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface NavigationStore extends State {
|
interface NavigationStore extends State {
|
||||||
currentScreen: CurrentScreen;
|
currentRoute: RouteName;
|
||||||
currentScreenRaw: Partial<CurrentScreen>;
|
|
||||||
canGoBack?: boolean;
|
canGoBack?: boolean;
|
||||||
update: (
|
focusedRouteId?: string;
|
||||||
currentScreen: Omit<Partial<CurrentScreen>, "name"> & {
|
update: (currentScreen: RouteName) => void;
|
||||||
name: keyof RouteParams;
|
|
||||||
},
|
|
||||||
canGoBack?: boolean,
|
|
||||||
headerRightButtons?: HeaderRightButton[]
|
|
||||||
) => void;
|
|
||||||
headerRightButtons?: HeaderRightButton[];
|
headerRightButtons?: HeaderRightButton[];
|
||||||
buttonAction: () => void;
|
buttonAction: () => void;
|
||||||
setButtonAction: (buttonAction: () => void) => void;
|
setButtonAction: (buttonAction: () => void) => void;
|
||||||
|
setFocusedRouteId: (id?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useNavigationStore = create<NavigationStore>((set, get) => ({
|
const useNavigationStore = create<NavigationStore>((set, get) => ({
|
||||||
currentScreen: {
|
focusedRouteId: "Notes",
|
||||||
name: "Notes",
|
setFocusedRouteId: (id) => {
|
||||||
id: "notes_navigation",
|
|
||||||
title: "Notes",
|
|
||||||
type: "notes"
|
|
||||||
},
|
|
||||||
currentScreenRaw: { name: "Notes" },
|
|
||||||
canGoBack: false,
|
|
||||||
update: (currentScreen, canGoBack, headerRightButtons) => {
|
|
||||||
const color =
|
|
||||||
ColorValues[
|
|
||||||
currentScreen.color?.toLowerCase() as keyof typeof ColorValues
|
|
||||||
];
|
|
||||||
if (
|
|
||||||
JSON.stringify(currentScreen) === JSON.stringify(get().currentScreenRaw)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
set({
|
set({
|
||||||
currentScreen: {
|
focusedRouteId: id
|
||||||
name: currentScreen.name,
|
|
||||||
id:
|
|
||||||
currentScreen.id || currentScreen.name.toLowerCase() + "_navigation",
|
|
||||||
title: currentScreen.title || currentScreen.name,
|
|
||||||
type: currentScreen.type,
|
|
||||||
color: color,
|
|
||||||
notebookId: currentScreen.notebookId,
|
|
||||||
beta: currentScreen.beta
|
|
||||||
},
|
|
||||||
currentScreenRaw: currentScreen,
|
|
||||||
canGoBack,
|
|
||||||
headerRightButtons: headerRightButtons
|
|
||||||
});
|
});
|
||||||
|
console.log("CurrentRoute ID", id);
|
||||||
|
},
|
||||||
|
currentRoute: "Notes",
|
||||||
|
canGoBack: false,
|
||||||
|
update: (currentScreen) => {
|
||||||
|
set({
|
||||||
|
currentRoute: currentScreen
|
||||||
|
});
|
||||||
|
console.log("CurrentRoute", currentScreen);
|
||||||
},
|
},
|
||||||
headerRightButtons: [],
|
headerRightButtons: [],
|
||||||
buttonAction: () => null,
|
buttonAction: () => null,
|
||||||
|
|||||||
2
apps/theme-builder/package-lock.json
generated
2
apps/theme-builder/package-lock.json
generated
@@ -1404,6 +1404,7 @@
|
|||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
|
"@tanstack/react-virtual": "^3.0.0-beta.68",
|
||||||
"@theme-ui/color": "^0.14.7",
|
"@theme-ui/color": "^0.14.7",
|
||||||
"@theme-ui/components": "^0.14.7",
|
"@theme-ui/components": "^0.14.7",
|
||||||
"@theme-ui/core": "^0.14.7",
|
"@theme-ui/core": "^0.14.7",
|
||||||
@@ -1439,7 +1440,6 @@
|
|||||||
"react-modal": "3.13.1",
|
"react-modal": "3.13.1",
|
||||||
"react-qrcode-logo": "^2.2.1",
|
"react-qrcode-logo": "^2.2.1",
|
||||||
"react-scroll-sync": "^0.9.0",
|
"react-scroll-sync": "^0.9.0",
|
||||||
"react-virtuoso": "^4.4.2",
|
|
||||||
"timeago.js": "4.0.2",
|
"timeago.js": "4.0.2",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"w3c-keyname": "^2.2.6",
|
"w3c-keyname": "^2.2.6",
|
||||||
|
|||||||
Reference in New Issue
Block a user