mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +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";
|
||||
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)
|
||||
return {
|
||||
rows: result.rows?._array || []
|
||||
|
||||
@@ -22,7 +22,6 @@ import { KeyboardAvoidingView, Platform } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { Header } from "../header";
|
||||
import SelectionHeader from "../selection-header";
|
||||
|
||||
export const Container = ({ children }: PropsWithChildren) => {
|
||||
@@ -46,7 +45,6 @@ export const Container = ({ children }: PropsWithChildren) => {
|
||||
{!introCompleted ? null : (
|
||||
<>
|
||||
<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 { Platform, StyleSheet, View } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { SearchBar } from "../../screens/search/search-bar";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { LeftMenus } from "./left-menus";
|
||||
import { RightMenus } from "./right-menus";
|
||||
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 insets = useGlobalSafeAreaInsets();
|
||||
const [hide, setHide] = useState(true);
|
||||
const [borderHidden, setBorderHidden] = useState(true);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const currentScreen = useNavigationStore(
|
||||
(state) => state.currentScreen?.name
|
||||
);
|
||||
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
|
||||
|
||||
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 (!hide) return;
|
||||
setHide(false);
|
||||
if (!borderHidden) return;
|
||||
setBorderHidden(false);
|
||||
} else {
|
||||
if (hide) return;
|
||||
setHide(true);
|
||||
if (borderHidden) return;
|
||||
setBorderHidden(true);
|
||||
}
|
||||
},
|
||||
[hide]
|
||||
[borderHidden, id, renderedInRoute]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,9 +91,9 @@ const _Header = () => {
|
||||
return () => {
|
||||
eUnSubscribeEvent(eScrollEvent, onScroll);
|
||||
};
|
||||
}, [hide, onScroll]);
|
||||
}, [borderHidden, onScroll]);
|
||||
|
||||
return selectionMode ? null : (
|
||||
return selectionMode && isFocused ? null : (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
@@ -72,29 +103,42 @@ const _Header = () => {
|
||||
backgroundColor: colors.primary.background,
|
||||
overflow: "hidden",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: hide
|
||||
borderBottomColor: borderHidden
|
||||
? "transparent"
|
||||
: colors.secondary.background,
|
||||
justifyContent: "space-between"
|
||||
}
|
||||
]}
|
||||
>
|
||||
{currentScreen === "Search" ? (
|
||||
<SearchBar />
|
||||
) : (
|
||||
<>
|
||||
<View style={styles.leftBtnContainer}>
|
||||
<LeftMenus />
|
||||
<Title />
|
||||
</View>
|
||||
<RightMenus />
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<View style={styles.leftBtnContainer}>
|
||||
<LeftMenus
|
||||
canGoBack={canGoBack}
|
||||
onLeftButtonPress={onLeftMenuButtonPress}
|
||||
/>
|
||||
|
||||
<Title
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const Header = React.memo(_Header, () => true);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import Navigation from "../../services/navigation";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const LeftMenus = () => {
|
||||
export const LeftMenus = ({
|
||||
canGoBack,
|
||||
onLeftButtonPress
|
||||
}: {
|
||||
canGoBack?: boolean;
|
||||
onLeftButtonPress?: () => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const canGoBack = useNavigationStore((state) => state.canGoBack);
|
||||
const isTablet = deviceMode === "tablet";
|
||||
|
||||
const onLeftButtonPress = () => {
|
||||
const _onLeftButtonPress = () => {
|
||||
if (onLeftButtonPress) return onLeftButtonPress();
|
||||
|
||||
if (!canGoBack) {
|
||||
if (tabBarRef.current?.isDrawerOpen()) {
|
||||
Navigation.closeDrawer();
|
||||
@@ -60,7 +66,7 @@ export const LeftMenus = () => {
|
||||
left={40}
|
||||
top={40}
|
||||
right={DDS.isLargeTablet() ? 10 : 10}
|
||||
onPress={onLeftButtonPress}
|
||||
onPress={_onLeftButtonPress}
|
||||
onLongPress={() => {
|
||||
Navigation.popToTop();
|
||||
}}
|
||||
|
||||
@@ -20,40 +20,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useRef } from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
//@ts-ignore
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import Menu from "react-native-reanimated-material-menu";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import Navigation from "../../services/navigation";
|
||||
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 { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const RightMenus = () => {
|
||||
export const RightMenus = ({
|
||||
headerRightButtons,
|
||||
renderedInRoute,
|
||||
id,
|
||||
onPressDefaultRightButton,
|
||||
search,
|
||||
onSearch
|
||||
}: {
|
||||
headerRightButtons?: HeaderRightButton[];
|
||||
renderedInRoute: RouteName;
|
||||
id?: string;
|
||||
onPressDefaultRightButton?: () => void;
|
||||
search?: boolean;
|
||||
onSearch?: () => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
||||
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);
|
||||
|
||||
return (
|
||||
<View style={styles.rightBtnContainer}>
|
||||
{!currentScreen.startsWith("Settings") ? (
|
||||
{search ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
SearchService.prepareSearch();
|
||||
Navigation.navigate(
|
||||
{
|
||||
name: "Search"
|
||||
},
|
||||
{}
|
||||
);
|
||||
}}
|
||||
onPress={onSearch}
|
||||
testID="icon-search"
|
||||
name="magnify"
|
||||
color={colors.primary.paragraph}
|
||||
@@ -63,9 +69,9 @@ export const RightMenus = () => {
|
||||
|
||||
{deviceMode !== "mobile" ? (
|
||||
<Button
|
||||
onPress={buttonAction}
|
||||
onPress={onPressDefaultRightButton}
|
||||
testID={notesnook.ids.default.addBtn}
|
||||
icon={currentScreen === "Trash" ? "delete" : "plus"}
|
||||
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
||||
iconSize={SIZE.xl}
|
||||
type="shade"
|
||||
hitSlop={{
|
||||
@@ -86,7 +92,7 @@ export const RightMenus = () => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{buttons && buttons.length > 0 ? (
|
||||
{headerRightButtons && headerRightButtons.length > 0 ? (
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
@@ -108,7 +114,7 @@ export const RightMenus = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{buttons.map((item) => (
|
||||
{headerRightButtons.map((item) => (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
|
||||
@@ -20,105 +20,74 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Tag from "../ui/tag";
|
||||
import Heading from "../ui/typography/heading";
|
||||
|
||||
const titleState: { [id: string]: boolean } = {};
|
||||
|
||||
export const Title = () => {
|
||||
export const Title = ({
|
||||
title,
|
||||
isHiddenOnRender,
|
||||
accentColor,
|
||||
isBeta,
|
||||
renderedInRoute,
|
||||
id
|
||||
}: {
|
||||
title: string;
|
||||
isHiddenOnRender?: boolean;
|
||||
accentColor?: string;
|
||||
isBeta?: boolean;
|
||||
renderedInRoute: string;
|
||||
id?: string;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const isNotebook = currentScreen.name === "Notebook";
|
||||
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 [visible, setVisible] = useState(isHiddenOnRender);
|
||||
const isTag = title.startsWith("#");
|
||||
const onScroll = useCallback(
|
||||
(data: { x: number; y: number }) => {
|
||||
if (currentScreen.name !== "Notebook") {
|
||||
setHide(false);
|
||||
return;
|
||||
}
|
||||
(data: { x: number; y: number; id?: string; route: string }) => {
|
||||
if (data.route !== renderedInRoute || data.id !== id) return;
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
titleState[currentScreen.id as string] = false;
|
||||
setHide(false);
|
||||
if (!visible) return;
|
||||
setVisible(false);
|
||||
} else {
|
||||
if (hide) return;
|
||||
titleState[currentScreen.id as string] = true;
|
||||
setHide(true);
|
||||
if (visible) return;
|
||||
setVisible(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(() => {
|
||||
eSubscribeEvent(eScrollEvent, onScroll);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eScrollEvent, onScroll);
|
||||
};
|
||||
}, [hide, onScroll]);
|
||||
}, [visible, onScroll]);
|
||||
|
||||
function navigateToNotebook() {
|
||||
if (!isTopic) return;
|
||||
if (notebook) {
|
||||
NotebookScreen.navigate(notebook, true);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!hide && !isHidden ? (
|
||||
{!visible ? (
|
||||
<Heading
|
||||
onPress={navigateToNotebook}
|
||||
numberOfLines={1}
|
||||
size={SIZE.lg}
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||
}}
|
||||
color={currentScreen.color || colors.primary.heading}
|
||||
color={accentColor || colors.primary.heading}
|
||||
>
|
||||
{isTag ? (
|
||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
{title}{" "}
|
||||
{isTag ? title.slice(1) : title}{" "}
|
||||
<Tag
|
||||
visible={currentScreen.beta}
|
||||
visible={isBeta}
|
||||
text="BETA"
|
||||
style={{
|
||||
backgroundColor: "transparent"
|
||||
|
||||
@@ -120,6 +120,7 @@ export const SectionHeader = React.memo<
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
console.log("Opening Sort sheet", screen, dataType);
|
||||
presentSheet({
|
||||
component: <Sort screen={screen} type={dataType} />
|
||||
});
|
||||
@@ -169,7 +170,7 @@ export const SectionHeader = React.memo<
|
||||
SettingsService.set({
|
||||
[dataType !== "notebook"
|
||||
? "notesListMode"
|
||||
: "notebooksListMode"]: isCompactModeEnabled
|
||||
: "notebooksListMode"]: !isCompactModeEnabled
|
||||
? "compact"
|
||||
: "normal"
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ const NoteItem = ({
|
||||
{notebooks?.items
|
||||
?.filter(
|
||||
(item) =>
|
||||
item.id !== useNavigationStore.getState().currentScreen?.id
|
||||
item.id !== useNavigationStore.getState().currentRoute?.id
|
||||
)
|
||||
.map((item) => (
|
||||
<Button
|
||||
|
||||
@@ -49,98 +49,96 @@ type EmptyListProps = {
|
||||
screen?: string;
|
||||
};
|
||||
|
||||
export const Empty = React.memo(
|
||||
function Empty({
|
||||
loading = true,
|
||||
placeholder,
|
||||
title,
|
||||
color,
|
||||
dataType,
|
||||
screen
|
||||
}: EmptyListProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const { height } = useWindowDimensions();
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
export const Empty = React.memo(function Empty({
|
||||
loading = true,
|
||||
placeholder,
|
||||
title,
|
||||
color,
|
||||
dataType,
|
||||
screen
|
||||
}: EmptyListProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const { height } = useWindowDimensions();
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
|
||||
const tip = useTip(
|
||||
screen === "Notes" && introCompleted
|
||||
? "first-note"
|
||||
: placeholder?.type || ((dataType + "s") as any),
|
||||
screen === "Notes" ? "notes" : "list"
|
||||
);
|
||||
const tip = useTip(
|
||||
screen === "Notes" && introCompleted
|
||||
? "first-note"
|
||||
: placeholder?.type || ((dataType + "s") as any),
|
||||
screen === "Notes" ? "notes" : "list"
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
height: height - (140 + insets.top),
|
||||
width: "80%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center"
|
||||
}
|
||||
]}
|
||||
>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Tip
|
||||
color={color ? color : "accent"}
|
||||
tip={tip || ({ text: placeholder?.paragraph } as TTip)}
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
height: height - (140 + insets.top),
|
||||
width: "80%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center"
|
||||
}
|
||||
]}
|
||||
>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Tip
|
||||
color={color}
|
||||
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={{
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 0
|
||||
alignSelf: "flex-start",
|
||||
borderRadius: 5,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
{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={{
|
||||
alignSelf: "flex-start",
|
||||
borderRadius: 5,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
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;
|
||||
}
|
||||
);
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
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>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Make a tips manager.
|
||||
|
||||
@@ -201,6 +201,7 @@ export default function List(props: ListProps) {
|
||||
dataType={props.dataType}
|
||||
color={props.customAccentColor}
|
||||
placeholder={props.placeholder}
|
||||
screen={props.renderedInRoute}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ async function resolveNotes(ids: string[]) {
|
||||
group.notebooks.map((id) => resolved.notebooks[id])
|
||||
),
|
||||
attachmentsCount:
|
||||
(await db.attachments?.ofNote(noteId, "all"))?.length || 0
|
||||
(await db.attachments?.ofNote(noteId, "all").ids())?.length || 0
|
||||
};
|
||||
}
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
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 { db } from "../../common/database";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
||||
import {
|
||||
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 { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { eClearEditor } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { eClearEditor } from "../../utils/events";
|
||||
|
||||
export default function Notebooks({ note, close, full }) {
|
||||
const { colors } = useThemeColors();
|
||||
const notebooks = useNotebookStore((state) => state.notebooks);
|
||||
async function getNotebooks(item) {
|
||||
let filteredNotebooks = [];
|
||||
const relations = await db.relations.to(note, "notebook").resolve();
|
||||
|
||||
filteredNotebooks.push(relations);
|
||||
|
||||
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
||||
return filteredNotebooks;
|
||||
}
|
||||
const [noteNotebooks, setNoteNotebooks] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
|
||||
});
|
||||
@@ -60,11 +49,6 @@ export default function Notebooks({ note, close, full }) {
|
||||
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) => (
|
||||
<View
|
||||
key={item.id}
|
||||
@@ -105,56 +89,6 @@ export default function Notebooks({ note, close, full }) {
|
||||
>
|
||||
{item.title}
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
@@ -46,8 +46,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
);
|
||||
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
|
||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const screen = currentScreen.name;
|
||||
const currentRoute = useNavigationStore((state) => state.currentRoute);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
SearchService.prepareSearch?.();
|
||||
const allItems = SearchService.getSearchInformation()?.get() || [];
|
||||
@@ -205,9 +204,9 @@ export const SelectionHeader = React.memo(() => {
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
{screen === "Trash" ||
|
||||
screen === "Notebooks" ||
|
||||
screen === "Reminders" ? null : (
|
||||
{currentRoute === "Trash" ||
|
||||
currentRoute === "Notebooks" ||
|
||||
currentRoute === "Reminders" ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
@@ -256,17 +255,15 @@ export const SelectionHeader = React.memo(() => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{screen === "TopicNotes" || screen === "Notebook" ? (
|
||||
{currentRoute === "Notebook" ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
if (selectedItemsList.length > 0) {
|
||||
const currentScreen =
|
||||
useNavigationStore.getState().currentScreen;
|
||||
|
||||
if (screen === "Notebook") {
|
||||
const { focusedRouteId } = useNavigationStore.getState();
|
||||
if (currentRoute === "Notebook") {
|
||||
for (const item of selectedItemsList) {
|
||||
await db.relations.unlink(
|
||||
{ type: "notebook", id: currentScreen.id },
|
||||
{ type: "notebook", id: focusedRouteId },
|
||||
item
|
||||
);
|
||||
}
|
||||
@@ -287,9 +284,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
tooltipText={`Remove from ${
|
||||
screen === "Notebook" ? "notebook" : "topic"
|
||||
}`}
|
||||
tooltipText={`Remove from Notebook`}
|
||||
tooltipPosition={4}
|
||||
testID="select-minus"
|
||||
color={colors.primary.paragraph}
|
||||
@@ -298,7 +293,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{screen === "Favorites" ? (
|
||||
{currentRoute === "Favorites" ? (
|
||||
<IconButton
|
||||
onPress={addToFavorite}
|
||||
customStyle={{
|
||||
@@ -312,7 +307,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{screen === "Trash" ? null : (
|
||||
{currentRoute === "Trash" ? null : (
|
||||
<IconButton
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
@@ -328,7 +323,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{screen === "Trash" ? (
|
||||
{currentRoute === "Trash" ? (
|
||||
<>
|
||||
<IconButton
|
||||
customStyle={{
|
||||
|
||||
@@ -38,6 +38,7 @@ import Seperator from "../../ui/seperator";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import { MoveNotes } from "../move-notes/movenote";
|
||||
import { eOnNotebookUpdated } from "../../../utils/events";
|
||||
import { getParentNotebookId } from "../../../utils/notebooks";
|
||||
|
||||
export const AddNotebookSheet = ({
|
||||
notebook,
|
||||
@@ -84,9 +85,12 @@ export const AddNotebookSheet = ({
|
||||
useMenuStore.getState().setMenuPins();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
eSendEvent(eOnNotebookUpdated, parentNotebook?.id);
|
||||
if (notebook) {
|
||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||
const parent = await getParentNotebookId(notebook.id);
|
||||
eSendEvent(eOnNotebookUpdated, parent);
|
||||
setImmediate(() => {
|
||||
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||
});
|
||||
}
|
||||
|
||||
if (!notebook) {
|
||||
@@ -136,6 +140,11 @@ export const AddNotebookSheet = ({
|
||||
onChangeText={(value) => {
|
||||
title.current = value;
|
||||
}}
|
||||
onLayout={() => {
|
||||
setImmediate(() => {
|
||||
titleInput?.current?.focus();
|
||||
});
|
||||
}}
|
||||
placeholder="Enter a title"
|
||||
onSubmit={() => {
|
||||
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({
|
||||
context: context,
|
||||
component: (ref, close) => (
|
||||
<AddNotebookSheet
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { Note, Notebook } from "@notesnook/core";
|
||||
import { GroupHeader, Note } from "@notesnook/core";
|
||||
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 { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { ActionSheetRef, FlashList } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
@@ -36,24 +35,53 @@ import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { SelectionProvider } from "./context";
|
||||
import { FilteredList } from "./filtered-list";
|
||||
import { ListItem } from "./list-item";
|
||||
import { useItemSelectionStore } from "./store";
|
||||
import { NotebookItem } from "./notebook-item";
|
||||
import { useNotebookItemSelectionStore } from "./store";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
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 = ({
|
||||
note,
|
||||
actionSheetRef
|
||||
}: {
|
||||
note: Note;
|
||||
note: Note | undefined;
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -63,72 +91,42 @@ const MoveNoteSheet = ({
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||
|
||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
||||
const multiSelect = useNotebookItemSelectionStore(
|
||||
(state) => state.multiSelect
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const items = note
|
||||
? [note.id]
|
||||
: (selectedItemsList as Note[]).map((note) => note.id);
|
||||
updateInitialSelectionState(items);
|
||||
return () => {
|
||||
useItemSelectionStore.getState().setMultiSelect(false);
|
||||
useItemSelectionStore.getState().setItemState({});
|
||||
useNotebookItemSelectionStore.setState({
|
||||
initialState: {},
|
||||
selection: {},
|
||||
multiSelect: false,
|
||||
canEnableMultiSelectMode: true
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
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]
|
||||
);
|
||||
}, [note, selectedItemsList]);
|
||||
|
||||
const onSave = async () => {
|
||||
const noteIds = note
|
||||
? [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);
|
||||
if (!item) continue;
|
||||
if (itemState[id] === "selected") {
|
||||
for (let noteId of noteIds) {
|
||||
await db.relations.add(item, { id: noteId, type: "note" });
|
||||
if (changedNotebooks[id] === "selected") {
|
||||
for (const id of noteIds) {
|
||||
await db.relations.add(item, { id: id, type: "note" });
|
||||
}
|
||||
} else if (itemState[id] === "deselected") {
|
||||
for (let noteId of noteIds) {
|
||||
await db.relations.unlink(item, { id: noteId, type: "note" });
|
||||
} else if (changedNotebooks[id] === "deselected") {
|
||||
for (const id of noteIds) {
|
||||
await db.relations.unlink(item, { id: id, type: "note" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,9 +139,18 @@ const MoveNoteSheet = ({
|
||||
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 (
|
||||
<>
|
||||
<Dialog context="move_note" />
|
||||
<SheetProvider context="link-notebooks" />
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
@@ -204,127 +211,50 @@ const MoveNoteSheet = ({
|
||||
}}
|
||||
type="grayAccent"
|
||||
onPress={() => {
|
||||
useItemSelectionStore.setState({
|
||||
itemState: {}
|
||||
});
|
||||
const items = note
|
||||
? [note.id]
|
||||
: (selectedItemsList as Note[]).map((note) => note.id);
|
||||
updateInitialSelectionState(items);
|
||||
}}
|
||||
/>
|
||||
</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={{
|
||||
paddingHorizontal: 12,
|
||||
maxHeight: dimensions.height * 0.85,
|
||||
height: 50 * ((notebooks?.ids.length || 0) + 2)
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<FilteredList
|
||||
ListEmptyComponent={
|
||||
notebooks?.ids.length ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="book-outline"
|
||||
color={colors.primary.icon}
|
||||
size={100}
|
||||
/>
|
||||
<Paragraph style={{ marginBottom: 10 }}>
|
||||
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>
|
||||
estimatedItemSize={50}
|
||||
keyExtractor={(item) => item as string}
|
||||
renderItem={renderNotebook}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.primary.icon}>No notebooks</Paragraph>
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MoveNoteSheet.present = (note) => {
|
||||
MoveNoteSheet.present = (note?: Note) => {
|
||||
presentSheet({
|
||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
||||
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
|
||||
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<
|
||||
string,
|
||||
"intermediate" | "selected" | "deselected"
|
||||
>;
|
||||
|
||||
export interface SelectionStore extends State {
|
||||
itemState: SelectionItemState;
|
||||
setItemState: (state: SelectionItemState) => void;
|
||||
multiSelect: boolean;
|
||||
setMultiSelect: (multiSelect: boolean) => void;
|
||||
}
|
||||
|
||||
export const useItemSelectionStore = create<SelectionStore>((set) => ({
|
||||
itemState: {},
|
||||
setItemState: (itemState) => {
|
||||
export const useNotebookExpandedStore = create<{
|
||||
expanded: {
|
||||
[id: string]: boolean;
|
||||
};
|
||||
setExpanded: (id: string) => void;
|
||||
}>((set, get) => ({
|
||||
expanded: {},
|
||||
setExpanded(id: string) {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
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 React, {
|
||||
RefObject,
|
||||
@@ -28,12 +29,21 @@ import React, {
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import { TextInput, View, useWindowDimensions } from "react-native";
|
||||
import {
|
||||
ActionSheetRef,
|
||||
FlashList,
|
||||
FlatList
|
||||
} from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import { useDBItem } from "../../../hooks/use-db-item";
|
||||
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import {
|
||||
ItemSelection,
|
||||
createItemSelectionStore
|
||||
} from "../../../stores/item-selection-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -42,17 +52,40 @@ import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
|
||||
function tagHasSomeNotes(tagId: string, noteIds: string[]) {
|
||||
return db.relations.from({ type: "tag", id: tagId }, "note").has(...noteIds);
|
||||
async function updateInitialSelectionState(items: string[]) {
|
||||
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[]) {
|
||||
return db.relations
|
||||
.from({ type: "tag", id: tagId }, "note")
|
||||
.hasAll(...noteIds);
|
||||
}
|
||||
const useTagItemSelection = createItemSelectionStore(true);
|
||||
|
||||
const ManageTagsSheet = (props: {
|
||||
notes?: Note[];
|
||||
@@ -60,12 +93,44 @@ const ManageTagsSheet = (props: {
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
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 inputRef = useRef<TextInput>(null);
|
||||
const [focus, setFocus] = 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) => {
|
||||
db.tags.all
|
||||
@@ -114,6 +179,7 @@ const ManageTagsSheet = (props: {
|
||||
|
||||
useRelationStore.getState().update();
|
||||
useTagStore.getState().setTags();
|
||||
refreshTags();
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: "Cannot add tag",
|
||||
@@ -126,13 +192,64 @@ const ManageTagsSheet = (props: {
|
||||
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 (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
minHeight: focus ? "100%" : "60%"
|
||||
maxHeight: dimensions.height * 0.85
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -161,31 +278,35 @@ const ManageTagsSheet = (props: {
|
||||
placeholder="Search or add a tag"
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
overScrollMode="never"
|
||||
scrollToOverflowEnabled={false}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
>
|
||||
{query && !queryExists ? (
|
||||
<PressableButton
|
||||
key={"query_item"}
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
onPress={onSubmit}
|
||||
type="selected"
|
||||
>
|
||||
<Heading size={SIZE.sm} color={colors.selected.heading}>
|
||||
Add {'"' + "#" + query + '"'}
|
||||
</Heading>
|
||||
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
||||
</PressableButton>
|
||||
) : null}
|
||||
{!tags || tags.ids.length === 0 ? (
|
||||
{query && !queryExists ? (
|
||||
<PressableButton
|
||||
key={"query_item"}
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
onPress={onSubmit}
|
||||
type="selected"
|
||||
>
|
||||
<Heading size={SIZE.sm} color={colors.selected.heading}>
|
||||
Add {'"' + "#" + query + '"'}
|
||||
</Heading>
|
||||
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
||||
</PressableButton>
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
data={tags?.ids?.filter((id) => typeof id === "string") as string[]}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
keyboardShouldPersistTaps
|
||||
keyboardDismissMode="interactive"
|
||||
keyExtractor={(item) => item as string}
|
||||
renderItem={renderTag}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -204,19 +325,9 @@ const ManageTagsSheet = (props: {
|
||||
You do not have any tags.
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{tags?.ids
|
||||
.filter((id) => !isGroupHeader(id))
|
||||
.map((item) => (
|
||||
<TagItem
|
||||
key={item as string}
|
||||
tags={tags}
|
||||
id={item as string}
|
||||
notes={notes}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
}
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -233,69 +344,17 @@ export default ManageTagsSheet;
|
||||
|
||||
const TagItem = ({
|
||||
id,
|
||||
notes,
|
||||
tags
|
||||
tags,
|
||||
onPress
|
||||
}: {
|
||||
id: string;
|
||||
notes: Note[];
|
||||
tags: VirtualizedGrouping<Tag>;
|
||||
onPress: (id: string) => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [tag, setTag] = useState<Tag>();
|
||||
const [selection, setSelection] = useState({
|
||||
all: false,
|
||||
some: false
|
||||
});
|
||||
const update = useRelationStore((state) => state.updater);
|
||||
const [tag] = useDBItem(id, "tag", tags);
|
||||
const selection = useTagItemSelection((state) => state.selection[id]);
|
||||
|
||||
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 (
|
||||
<PressableButton
|
||||
customStyle={{
|
||||
@@ -304,34 +363,32 @@ const TagItem = ({
|
||||
justifyContent: "flex-start",
|
||||
height: 40
|
||||
}}
|
||||
onPress={onPress}
|
||||
onPress={() => onPress(id)}
|
||||
type="gray"
|
||||
>
|
||||
{!tag ? null : (
|
||||
<IconButton
|
||||
<Icon
|
||||
size={22}
|
||||
customStyle={{
|
||||
marginRight: 5,
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
onPress={onPress}
|
||||
onPress={() => onPress(id)}
|
||||
color={
|
||||
selection.some || selection.all
|
||||
selection === "selected" || selection === "intermediate"
|
||||
? colors.selected.icon
|
||||
: colors.primary.icon
|
||||
}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
testID={
|
||||
selection.all
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection.some
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
name={
|
||||
selection.all
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection.some
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
@@ -344,7 +401,7 @@ const TagItem = ({
|
||||
style={{
|
||||
width: 200,
|
||||
height: 30,
|
||||
backgroundColor: colors.secondary.background,
|
||||
// backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -52,6 +52,24 @@ import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
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 };
|
||||
class NotebookSheetConfig {
|
||||
static storageKey: "$$sp";
|
||||
@@ -88,8 +106,10 @@ const useNotebookExpandedStore = create<{
|
||||
|
||||
export const NotebookSheet = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const canShow = currentScreen.name === "Notebook";
|
||||
const currentRoute = useNavigationStore((state) => state.currentRoute);
|
||||
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
|
||||
|
||||
const canShow = currentRoute === "Notebook";
|
||||
const [selection, setSelection] = useState<Notebook[]>([]);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
@@ -103,7 +123,7 @@ export const NotebookSheet = () => {
|
||||
nestedNotebooks: notebooks,
|
||||
nestedNotebookNotesCount: totalNotes,
|
||||
groupOptions
|
||||
} = useNotebook(currentScreen.name === "Notebook" ? root : undefined);
|
||||
} = useNotebook(currentRoute === "Notebook" ? root : undefined);
|
||||
|
||||
const PLACEHOLDER_DATA = {
|
||||
heading: "Notebooks",
|
||||
@@ -158,8 +178,8 @@ export const NotebookSheet = () => {
|
||||
useEffect(() => {
|
||||
if (canShow) {
|
||||
setTimeout(async () => {
|
||||
const id = currentScreen?.id;
|
||||
const nextRoot = await findRootNotebookId(id);
|
||||
if (!focusedRouteId) return;
|
||||
const nextRoot = await findRootNotebookId(focusedRouteId);
|
||||
setRoot(nextRoot);
|
||||
if (nextRoot !== currentItem.current) {
|
||||
setSelection([]);
|
||||
@@ -183,7 +203,7 @@ export const NotebookSheet = () => {
|
||||
setEnabled(false);
|
||||
ref.current?.hide();
|
||||
}
|
||||
}, [canShow, currentScreen?.id, currentScreen.name, onRequestUpdate]);
|
||||
}, [canShow, currentRoute, onRequestUpdate, focusedRouteId]);
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
@@ -206,7 +226,7 @@ export const NotebookSheet = () => {
|
||||
NotebookSheetConfig.set(
|
||||
{
|
||||
type: "notebook",
|
||||
id: currentScreen.id as string
|
||||
id: focusedRouteId as string
|
||||
},
|
||||
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 = ({
|
||||
id,
|
||||
totalNotes,
|
||||
@@ -430,12 +432,12 @@ const NotebookItem = ({
|
||||
nestedNotebooks,
|
||||
notebook: item
|
||||
} = useNotebook(id, items);
|
||||
const screen = useNavigationStore((state) => state.currentScreen);
|
||||
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const selection = useSelection();
|
||||
const isSelected =
|
||||
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
|
||||
const isFocused = screen.id === id;
|
||||
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||
|
||||
|
||||
@@ -36,8 +36,9 @@ const Sort = ({ type, screen }) => {
|
||||
db.settings.getGroupOptions(screen === "Notes" ? "home" : type + "s")
|
||||
);
|
||||
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);
|
||||
setTimeout(() => {
|
||||
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
|
||||
|
||||
@@ -66,7 +66,7 @@ const ColorItem = React.memo(
|
||||
const onHeaderStateChange = useCallback(
|
||||
(state: any) => {
|
||||
setTimeout(() => {
|
||||
let id = state.currentScreen?.id;
|
||||
let id = state.focusedRouteId;
|
||||
if (id === item.id) {
|
||||
setHeaderTextState({ id: state.currentScreen.id });
|
||||
} else {
|
||||
|
||||
@@ -34,10 +34,9 @@ export const MenuItem = React.memo(
|
||||
function MenuItem({ item, index, testID, rightBtn }) {
|
||||
const { colors } = useThemeColors();
|
||||
const [headerTextState, setHeaderTextState] = useState(
|
||||
useNavigationStore.getState().currentScreen
|
||||
useNavigationStore.getState().focusedRouteId
|
||||
);
|
||||
const screenId = item.name.toLowerCase() + "_navigation";
|
||||
let isFocused = headerTextState?.id === screenId;
|
||||
let isFocused = headerTextState?.id === item.name;
|
||||
const primaryColors = isFocused ? colors.selected : colors.primary;
|
||||
|
||||
const _onPress = () => {
|
||||
@@ -59,9 +58,9 @@ export const MenuItem = React.memo(
|
||||
const onHeaderStateChange = useCallback(
|
||||
(state) => {
|
||||
setTimeout(() => {
|
||||
let id = state.currentScreen?.id;
|
||||
if (id === screenId) {
|
||||
setHeaderTextState({ id: state.currentScreen.id });
|
||||
let id = state.focusedRouteId;
|
||||
if (id === item.name) {
|
||||
setHeaderTextState({ id: state.focusedRouteId });
|
||||
} else {
|
||||
if (headerTextState !== null) {
|
||||
setHeaderTextState(null);
|
||||
@@ -69,7 +68,7 @@ export const MenuItem = React.memo(
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
[headerTextState, screenId]
|
||||
[headerTextState, item.name]
|
||||
);
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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 Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import { TaggedNotes } from "../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { db } from "../../common/database";
|
||||
import { normalize, SIZE } from "../../utils/size";
|
||||
import { SIZE, normalize } from "../../utils/size";
|
||||
import { Properties } from "../properties";
|
||||
import { Button } from "../ui/button";
|
||||
import { Notice } from "../ui/notice";
|
||||
@@ -38,8 +38,6 @@ import Seperator from "../ui/seperator";
|
||||
import SheetWrapper from "../ui/sheet";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useCallback } from "react";
|
||||
import { Notebook, Tag } from "@notesnook/core";
|
||||
|
||||
export const TagsSection = React.memo(
|
||||
function TagsSection() {
|
||||
@@ -127,7 +125,7 @@ export const PinItem = React.memo(
|
||||
const onHeaderStateChange = useCallback(
|
||||
(state: any) => {
|
||||
setTimeout(() => {
|
||||
const id = state.currentScreen?.id;
|
||||
const id = state.focusedRouteId;
|
||||
if (id === item.id) {
|
||||
setHeaderTextState({
|
||||
id
|
||||
|
||||
@@ -452,7 +452,7 @@ export const useActions = ({
|
||||
}
|
||||
|
||||
async function showAttachments() {
|
||||
AttachmentDialog.present(item);
|
||||
AttachmentDialog.present(item as Note);
|
||||
}
|
||||
|
||||
async function exportNote() {
|
||||
@@ -486,12 +486,10 @@ export const useActions = ({
|
||||
};
|
||||
|
||||
async function removeNoteFromNotebook() {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (currentScreen.name !== "Notebook") return;
|
||||
await db.relations.unlink(
|
||||
{ type: "notebook", id: currentScreen.id },
|
||||
item
|
||||
);
|
||||
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
|
||||
if (currentRoute !== "Notebook" || !focusedRouteId) return;
|
||||
|
||||
await db.relations.unlink({ type: "notebook", id: focusedRouteId }, item);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
}
|
||||
@@ -499,7 +497,7 @@ export const useActions = ({
|
||||
function addTo() {
|
||||
clearSelection();
|
||||
setSelectedItem(item);
|
||||
MoveNoteSheet.present(item);
|
||||
MoveNoteSheet.present(item as Note);
|
||||
}
|
||||
|
||||
async function addToFavorites() {
|
||||
@@ -857,11 +855,13 @@ export const useActions = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
||||
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
|
||||
if (item.type !== "note" || currentRoute !== "Notebook" || !focusedRouteId)
|
||||
return;
|
||||
|
||||
!!db.relations
|
||||
.to(item, "notebook")
|
||||
.selector.find((v) => v("id", "==", currentScreen.id))
|
||||
.selector.find((v) => v("id", "==", focusedRouteId))
|
||||
.then((notebook) => {
|
||||
setNoteInCurrentNotebook(!!notebook);
|
||||
});
|
||||
|
||||
@@ -56,9 +56,12 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
const onUpdateItem = (itemId?: string) => {
|
||||
if (typeof itemId === "string" && itemId !== id) return;
|
||||
if (!id) {
|
||||
setItem(undefined);
|
||||
if (item) {
|
||||
setItem(undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log("onUpdateItem", id, type);
|
||||
|
||||
if (items) {
|
||||
items.item(id).then((item) => {
|
||||
@@ -82,7 +85,7 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
return () => {
|
||||
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||
};
|
||||
}, [id, type]);
|
||||
}, [id, type, items, item]);
|
||||
|
||||
return [
|
||||
item as ItemTypeKey[T],
|
||||
@@ -116,6 +119,7 @@ export const useTotalNotes = (
|
||||
}
|
||||
setTotalNotesById(totalNotesById);
|
||||
});
|
||||
console.log("useTotalNotes.getTotalNotes");
|
||||
}, [ids, type]);
|
||||
|
||||
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 { RefObject, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useRoute } from "@react-navigation/core";
|
||||
import useNavigationStore, { RouteName } from "../stores/use-navigation-store";
|
||||
|
||||
type NavigationFocus = {
|
||||
onFocus?: (prev: RefObject<boolean>) => boolean;
|
||||
@@ -31,6 +33,7 @@ export const useNavigationFocus = (
|
||||
navigation: NativeStackNavigationProp<Record<string, object | undefined>>,
|
||||
{ onFocus, onBlur, delay, focusOnInit = true }: NavigationFocus
|
||||
) => {
|
||||
const route = useRoute();
|
||||
const [isFocused, setFocused] = useState(focusOnInit);
|
||||
const prev = useRef(false);
|
||||
const isBlurred = useRef(false);
|
||||
@@ -39,6 +42,12 @@ export const useNavigationFocus = (
|
||||
setTimeout(
|
||||
() => {
|
||||
const shouldFocus = onFocus ? onFocus(prev) : true;
|
||||
|
||||
const routeName = route.name?.startsWith("Settings")
|
||||
? "Settings"
|
||||
: route.name;
|
||||
useNavigationStore.getState().update(routeName as RouteName);
|
||||
|
||||
if (shouldFocus) {
|
||||
setFocused(true);
|
||||
prev.current = true;
|
||||
|
||||
@@ -39,17 +39,19 @@ export const useNotebook = (
|
||||
|
||||
const onRequestUpdate = React.useCallback(() => {
|
||||
if (!item || !id) {
|
||||
console.log("unset notebook");
|
||||
setNotebooks(undefined);
|
||||
if (notebooks) {
|
||||
setNotebooks(undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log("useNotebook.onRequestUpdate", id);
|
||||
db.relations
|
||||
.from(item, "notebook")
|
||||
.selector.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
.then((notebooks) => {
|
||||
setNotebooks(notebooks);
|
||||
});
|
||||
}, [item, id]);
|
||||
}, [item, id, notebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
onRequestUpdate();
|
||||
@@ -75,7 +77,7 @@ export const useNotebook = (
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
||||
};
|
||||
}, [onUpdate, onRequestUpdate, id]);
|
||||
}, [onUpdate, onRequestUpdate, id, refresh]);
|
||||
|
||||
return {
|
||||
notebook: item,
|
||||
|
||||
@@ -35,7 +35,6 @@ import Notebooks from "../screens/notebooks";
|
||||
import { ColoredNotes } from "../screens/notes/colored";
|
||||
import { Monographs } from "../screens/notes/monographs";
|
||||
import { TaggedNotes } from "../screens/notes/tagged";
|
||||
import { TopicNotes } from "../screens/notes/topic-notes";
|
||||
import Reminders from "../screens/reminders";
|
||||
import { Search } from "../screens/search";
|
||||
import Settings from "../screens/settings";
|
||||
@@ -120,44 +119,14 @@ const _Tabs = () => {
|
||||
<NativeStack.Screen name="Welcome" component={IntroStackNavigator} />
|
||||
<NativeStack.Screen name="Notes" component={Home} />
|
||||
<NativeStack.Screen name="Notebooks" component={Notebooks} />
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Favorites"
|
||||
component={Favorites}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Trash"
|
||||
component={Trash}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Tags"
|
||||
component={Tags}
|
||||
/>
|
||||
<NativeStack.Screen name="Favorites" component={Favorites} />
|
||||
<NativeStack.Screen name="Trash" component={Trash} />
|
||||
<NativeStack.Screen name="Tags" component={Tags} />
|
||||
<NativeStack.Screen name="Settings" component={Settings} />
|
||||
<NativeStack.Screen name="TaggedNotes" component={TaggedNotes} />
|
||||
<NativeStack.Screen name="ColoredNotes" component={ColoredNotes} />
|
||||
<NativeStack.Screen name="Reminders" component={Reminders} />
|
||||
<NativeStack.Screen
|
||||
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"
|
||||
initialParams={{
|
||||
item: { type: "monograph" },
|
||||
@@ -166,16 +135,8 @@ const _Tabs = () => {
|
||||
}}
|
||||
component={Monographs}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Notebook"
|
||||
component={NotebookScreen}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
options={{ lazy: true }}
|
||||
name="Search"
|
||||
component={Search}
|
||||
/>
|
||||
<NativeStack.Screen name="Notebook" component={NotebookScreen} />
|
||||
<NativeStack.Screen name="Search" component={Search} />
|
||||
</NativeStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import SettingsService from "../../services/settings";
|
||||
import { useFavoriteStore } from "../../stores/use-favorite-store";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { Header } from "../../components/header";
|
||||
const prepareSearch = () => {
|
||||
SearchService.update({
|
||||
placeholder: "Search in favorites",
|
||||
@@ -50,9 +51,7 @@ export const Favorites = ({
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name
|
||||
});
|
||||
useNavigationStore.getState().setFocusedRouteId(route?.name);
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
return !prev?.current;
|
||||
},
|
||||
@@ -61,23 +60,43 @@ export const Favorites = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout wait={loading}>
|
||||
<List
|
||||
data={favorites}
|
||||
dataType="note"
|
||||
onRefresh={() => {
|
||||
setFavorites();
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={false}
|
||||
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 { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { openEditor } from "../notes/common";
|
||||
|
||||
const prepareSearch = () => {
|
||||
SearchService.update({
|
||||
placeholder: "Type a keyword to search in notes",
|
||||
type: "notes",
|
||||
title: "Notes",
|
||||
get: () => db.notes?.all
|
||||
});
|
||||
};
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
|
||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
const notes = useNoteStore((state) => state.notes);
|
||||
@@ -48,11 +38,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name
|
||||
});
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
useNavigationStore.getState().setButtonAction(openEditor);
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
return !prev?.current;
|
||||
},
|
||||
onBlur: () => false,
|
||||
@@ -60,23 +46,41 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout wait={loading} delay={500}>
|
||||
<List
|
||||
data={notes}
|
||||
dataType="note"
|
||||
renderedInRoute="Notes"
|
||||
loading={loading || !isFocused}
|
||||
headerTitle="Notes"
|
||||
placeholder={{
|
||||
title: "Notes",
|
||||
paragraph: "You have not added any notes yet.",
|
||||
button: "Add your first note",
|
||||
action: openEditor,
|
||||
loading: "Loading your notes"
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
Navigation.push("Search", {
|
||||
placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
|
||||
type: "note",
|
||||
title: route.name,
|
||||
route: route.name
|
||||
});
|
||||
}}
|
||||
id={route.name}
|
||||
onPressDefaultRightButton={openEditor}
|
||||
/>
|
||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||
</DelayLayout>
|
||||
<DelayLayout wait={loading} delay={500}>
|
||||
<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 { db } from "../../common/database";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
|
||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
@@ -46,7 +46,6 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
onFocus: () => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
syncWithNavigation();
|
||||
useNavigationStore.getState().setButtonAction(openEditor);
|
||||
return false;
|
||||
},
|
||||
onBlur: () => {
|
||||
@@ -56,21 +55,12 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
});
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
useNavigationStore.getState().update(
|
||||
{
|
||||
name: route.name,
|
||||
title: params.current?.title,
|
||||
id: params.current?.item?.id,
|
||||
type: "notebook"
|
||||
},
|
||||
params.current?.canGoBack
|
||||
);
|
||||
useNavigationStore.getState().setFocusedRouteId(params?.current?.item?.id);
|
||||
setOnFirstSave({
|
||||
type: "notebook",
|
||||
id: params.current.item.id
|
||||
});
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
}, [route.name]);
|
||||
}, []);
|
||||
|
||||
const onRequestUpdate = React.useCallback(
|
||||
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 (
|
||||
<>
|
||||
<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>
|
||||
<List
|
||||
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>
|
||||
</>
|
||||
@@ -179,19 +156,11 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
|
||||
NotebookScreen.navigate = (item: Notebook, canGoBack?: boolean) => {
|
||||
if (!item) return;
|
||||
Navigation.navigate<"Notebook">(
|
||||
{
|
||||
title: item.title,
|
||||
name: "Notebook",
|
||||
id: item.id,
|
||||
type: "notebook"
|
||||
},
|
||||
{
|
||||
title: item.title,
|
||||
item: item,
|
||||
canGoBack
|
||||
}
|
||||
);
|
||||
Navigation.navigate<"Notebook">("Notebook", {
|
||||
title: item.title,
|
||||
item: item,
|
||||
canGoBack
|
||||
});
|
||||
};
|
||||
|
||||
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 { Config } from "react-native-config";
|
||||
import { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
import { Walkthrough } from "../../components/walkthroughs";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNotebookStore } from "../../stores/use-notebook-store";
|
||||
|
||||
const onPressFloatingButton = () => {
|
||||
const onButtonPress = () => {
|
||||
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 = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -56,12 +46,7 @@ export const Notebooks = ({
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name
|
||||
});
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
||||
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
return !prev?.current;
|
||||
},
|
||||
onBlur: () => false,
|
||||
@@ -79,29 +64,47 @@ export const Notebooks = ({
|
||||
}, [notebooks]);
|
||||
|
||||
return (
|
||||
<DelayLayout delay={1}>
|
||||
<List
|
||||
data={notebooks}
|
||||
dataType="notebook"
|
||||
renderedInRoute="Notebooks"
|
||||
loading={!isFocused}
|
||||
placeholder={{
|
||||
title: "Your notebooks",
|
||||
paragraph: "You have not added any notebooks yet.",
|
||||
button: "Add your first notebook",
|
||||
action: onPressFloatingButton,
|
||||
loading: "Loading your notebooks"
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={route.params?.canGoBack}
|
||||
hasSearch={true}
|
||||
id={route.name}
|
||||
onSearch={() => {
|
||||
Navigation.push("Search", {
|
||||
placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
|
||||
type: "notebook",
|
||||
title: route.name,
|
||||
route: route.name
|
||||
});
|
||||
}}
|
||||
headerTitle="Notebooks"
|
||||
onPressDefaultRightButton={onButtonPress}
|
||||
/>
|
||||
|
||||
{!notebooks || notebooks.ids.length === 0 || !isFocused ? null : (
|
||||
<FloatingButton
|
||||
title="Create a new notebook"
|
||||
onPress={onPressFloatingButton}
|
||||
<DelayLayout delay={1}>
|
||||
<List
|
||||
data={notebooks}
|
||||
dataType="notebook"
|
||||
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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
||||
import React from "react";
|
||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
import NotesPage from ".";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { openEditor, toCamelCase } from "./common";
|
||||
import { PLACEHOLDER_DATA, openEditor, toCamelCase } from "./common";
|
||||
export const ColoredNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -54,18 +53,9 @@ ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
|
||||
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||
if (!item) return;
|
||||
Navigation.navigate<"ColoredNotes">(
|
||||
{
|
||||
name: "ColoredNotes",
|
||||
title: toCamelCase(item.title),
|
||||
id: item.id,
|
||||
type: "color",
|
||||
color: item.title?.toLowerCase()
|
||||
},
|
||||
{
|
||||
item: item,
|
||||
canGoBack,
|
||||
title: toCamelCase(item.title)
|
||||
}
|
||||
);
|
||||
Navigation.navigate<"ColoredNotes">("ColoredNotes", {
|
||||
item: item,
|
||||
canGoBack,
|
||||
title: toCamelCase(item.title)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -29,6 +29,14 @@ import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
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) {
|
||||
if (!title) return "";
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Color,
|
||||
GroupedItems,
|
||||
Item,
|
||||
Note,
|
||||
Topic
|
||||
} from "@notesnook/core/dist/types";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Color, Note } from "@notesnook/core/dist/types";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { PlaceholderData } from "../../components/list/empty";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
@@ -45,39 +38,11 @@ import useNavigationStore, {
|
||||
RouteName
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
|
||||
import NotebookScreen from "../notebook/index";
|
||||
import {
|
||||
openEditor,
|
||||
openMonographsWebpage,
|
||||
setOnFirstSave,
|
||||
toCamelCase
|
||||
} from "./common";
|
||||
import { PlaceholderData } from "../../components/list/empty";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { setOnFirstSave } from "./common";
|
||||
export const WARNING_DATA = {
|
||||
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> {
|
||||
get: (
|
||||
params: NotesScreenParams,
|
||||
@@ -93,7 +58,6 @@ export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
||||
function getItemType(routeName: RouteName) {
|
||||
if (routeName === "TaggedNotes") return "tag";
|
||||
if (routeName === "ColoredNotes") return "color";
|
||||
if (routeName === "TopicNotes") return "topic";
|
||||
if (routeName === "Monographs") return "monograph";
|
||||
return "note";
|
||||
}
|
||||
@@ -110,24 +74,24 @@ const NotesPage = ({
|
||||
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
||||
>) => {
|
||||
const params = useRef<NotesScreenParams>(route?.params);
|
||||
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
const [loadingNotes, setLoadingNotes] = useState(true);
|
||||
const isMonograph = route.name === "Monographs";
|
||||
|
||||
// const notebook =
|
||||
// route.name === "TopicNotes" &&
|
||||
// params.current.item.type === "topic" &&
|
||||
// params.current.item.notebookId
|
||||
// ? db.notebooks?.notebook((params.current.item as Topic).notebookId)?.data
|
||||
// : null;
|
||||
const title =
|
||||
params.current?.item.type === "tag"
|
||||
? "#" + params.current?.item.title
|
||||
: params.current?.item.title;
|
||||
const accentColor =
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined;
|
||||
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
|
||||
syncWithNavigation();
|
||||
|
||||
if (focusControl) return !prev.current;
|
||||
return false;
|
||||
},
|
||||
@@ -143,10 +107,7 @@ const NotesPage = ({
|
||||
SearchService.update({
|
||||
placeholder: `Search in ${item.title}`,
|
||||
type: "notes",
|
||||
title:
|
||||
item.type === "tag"
|
||||
? "#" + item.title
|
||||
: toCamelCase((item as Color).title),
|
||||
title: item.type === "tag" ? "#" + item.title : item.title,
|
||||
get: () => {
|
||||
return get(params.current, false);
|
||||
}
|
||||
@@ -154,31 +115,16 @@ const NotesPage = ({
|
||||
}, [get]);
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
const { item, title } = params.current;
|
||||
useNavigationStore.getState().update(
|
||||
{
|
||||
name: route.name,
|
||||
title:
|
||||
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);
|
||||
const { item } = params.current;
|
||||
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||
|
||||
!isMonograph &&
|
||||
setOnFirstSave({
|
||||
type: getItemType(route.name),
|
||||
id: item.id,
|
||||
notebook: item.type === "topic" ? item.notebookId : undefined
|
||||
id: item.id
|
||||
});
|
||||
}, [
|
||||
isMonograph,
|
||||
@@ -192,9 +138,6 @@ const NotesPage = ({
|
||||
async (data?: NotesScreenParams) => {
|
||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||
if (data) params.current = data;
|
||||
params.current.title =
|
||||
params.current.title ||
|
||||
(params.current.item as Item & { title: string }).title;
|
||||
const { item } = params.current;
|
||||
try {
|
||||
if (isNew) setLoadingNotes(true);
|
||||
@@ -204,9 +147,8 @@ const NotesPage = ({
|
||||
)) as VirtualizedGrouping<Note>;
|
||||
|
||||
if (
|
||||
((item.type === "tag" || item.type === "color") &&
|
||||
(!notes || notes.ids.length === 0)) ||
|
||||
(item.type === "topic" && !notes)
|
||||
(item.type === "tag" || item.type === "color") &&
|
||||
(!notes || notes.ids.length === 0)
|
||||
) {
|
||||
return Navigation.goBack();
|
||||
}
|
||||
@@ -243,81 +185,51 @@ const NotesPage = ({
|
||||
}, [onRequestUpdate, route.name]);
|
||||
|
||||
return (
|
||||
<DelayLayout
|
||||
color={
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined
|
||||
}
|
||||
wait={loading || loadingNotes}
|
||||
>
|
||||
{/* {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
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={title}
|
||||
canGoBack={params?.current?.canGoBack}
|
||||
hasSearch={true}
|
||||
id={
|
||||
route.name === "Monographs" ? "Monographs" : params?.current.item?.id
|
||||
}
|
||||
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 &&
|
||||
((notes?.ids && (notes?.ids?.length || 0) > 0) || isFocused) ? (
|
||||
<FloatingButton
|
||||
color={
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined
|
||||
}
|
||||
title="Create a note"
|
||||
onPress={onPressFloatingButton}
|
||||
<DelayLayout color={accentColor} wait={loading || loadingNotes}>
|
||||
<List
|
||||
data={notes}
|
||||
dataType="note"
|
||||
onRefresh={onRequestUpdate}
|
||||
loading={loading || !isFocused}
|
||||
renderedInRoute="Notes"
|
||||
headerTitle={title}
|
||||
customAccentColor={accentColor}
|
||||
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 NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
import NotesPage from ".";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { MonographType } from "../../utils/types";
|
||||
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 = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -33,7 +43,7 @@ export const Monographs = ({
|
||||
navigation={navigation}
|
||||
route={route}
|
||||
get={Monographs.get}
|
||||
placeholder={PLACEHOLDER_DATA}
|
||||
placeholder={MONOGRAPH_PLACEHOLDER_DATA}
|
||||
onPressFloatingButton={openMonographsWebpage}
|
||||
canGoBack={route.params?.canGoBack}
|
||||
focusControl={true}
|
||||
@@ -49,16 +59,10 @@ Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
|
||||
return await db.monographs.all.grouped(db.settings.getGroupOptions("notes"));
|
||||
};
|
||||
|
||||
Monographs.navigate = (item?: MonographType, canGoBack?: boolean) => {
|
||||
Navigation.navigate<"Monographs">(
|
||||
{
|
||||
name: "Monographs",
|
||||
type: "monograph"
|
||||
},
|
||||
{
|
||||
item: { type: "monograph" } as any,
|
||||
canGoBack: canGoBack as boolean,
|
||||
title: "Monographs"
|
||||
}
|
||||
);
|
||||
Monographs.navigate = (canGoBack?: boolean) => {
|
||||
Navigation.navigate<"Monographs">("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 React from "react";
|
||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||
import NotesPage from ".";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { openEditor } from "./common";
|
||||
import { PLACEHOLDER_DATA, openEditor } from "./common";
|
||||
|
||||
export const TaggedNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -53,17 +54,9 @@ TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
|
||||
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||
if (!item) return;
|
||||
Navigation.navigate<"TaggedNotes">(
|
||||
{
|
||||
name: "TaggedNotes",
|
||||
title: item.title,
|
||||
id: item.id,
|
||||
type: "tag"
|
||||
},
|
||||
{
|
||||
item: item,
|
||||
canGoBack,
|
||||
title: item.title
|
||||
}
|
||||
);
|
||||
Navigation.navigate<"TaggedNotes">("TaggedNotes", {
|
||||
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 { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import ReminderSheet from "../../components/sheets/reminder";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-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 = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -60,13 +40,8 @@ export const Reminders = ({
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name,
|
||||
beta: true
|
||||
});
|
||||
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
useNavigationStore.getState().setButtonAction(PLACEHOLDER_DATA.action);
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
return !prev?.current;
|
||||
},
|
||||
onBlur: () => false,
|
||||
@@ -74,23 +49,52 @@ export const Reminders = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout>
|
||||
<List
|
||||
data={reminders}
|
||||
dataType="reminder"
|
||||
headerTitle="Reminders"
|
||||
renderedInRoute="Reminders"
|
||||
loading={!isFocused}
|
||||
placeholder={PLACEHOLDER_DATA}
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
title="Set a new reminder"
|
||||
onPress={() => {
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
Navigation.push("Search", {
|
||||
placeholder: `Type a keyword to search in ${route.name}`,
|
||||
type: "reminder",
|
||||
title: route.name,
|
||||
route: route.name
|
||||
});
|
||||
}}
|
||||
id={route.name}
|
||||
onPressDefaultRightButton={() => {
|
||||
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;
|
||||
db.settings.setToolbarConfig(
|
||||
useSettingStore.getState().deviceMode || "mobile",
|
||||
useSettingStore.getState().deviceMode || ("mobile" as any),
|
||||
{
|
||||
preset: "custom",
|
||||
config: clone(_data)
|
||||
@@ -81,7 +81,7 @@ export const useDragState = create<DragState>(
|
||||
},
|
||||
setPreset: (preset) => {
|
||||
db.settings.setToolbarConfig(
|
||||
useSettingStore.getState().deviceMode || "mobile",
|
||||
useSettingStore.getState().deviceMode || ("mobile" as any),
|
||||
{
|
||||
preset,
|
||||
config: preset === "custom" ? clone(get().customPresetData) : []
|
||||
@@ -99,7 +99,7 @@ export const useDragState = create<DragState>(
|
||||
const user = await db.user?.getUser();
|
||||
if (!user) return;
|
||||
const toolbarConfig = db.settings.getToolbarConfig(
|
||||
useSettingStore.getState().deviceMode || "mobile"
|
||||
useSettingStore.getState().deviceMode || ("mobile" as any)
|
||||
);
|
||||
if (!toolbarConfig) {
|
||||
logger.info("DragState", "No user defined toolbar config was found");
|
||||
@@ -110,25 +110,20 @@ export const useDragState = create<DragState>(
|
||||
preset: preset,
|
||||
data:
|
||||
preset === "custom"
|
||||
? clone(toolbarConfig?.config)
|
||||
? clone(toolbarConfig?.config as any[])
|
||||
: clone(presets[preset]),
|
||||
customPresetData:
|
||||
preset === "custom"
|
||||
? clone(toolbarConfig?.config)
|
||||
? clone(toolbarConfig?.config as any[])
|
||||
: clone(presets["custom"])
|
||||
});
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "drag-state-storage", // unique name
|
||||
getStorage: () => MMKV as StateStorage,
|
||||
getStorage: () => MMKV as unknown as StateStorage,
|
||||
onRehydrateStorage: () => {
|
||||
return () => {
|
||||
logger.info(
|
||||
"DragState",
|
||||
"rehydrated drag state",
|
||||
useNoteStore.getState().loading
|
||||
);
|
||||
if (!useNoteStore.getState().loading) {
|
||||
useDragState.getState().init();
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { tabBarRef } from "../../utils/global-refs";
|
||||
import { components } from "./components";
|
||||
import { SectionItem } from "./section-item";
|
||||
import { RouteParams, SettingSection } from "./types";
|
||||
import { Header } from "../../components/header";
|
||||
|
||||
const keyExtractor = (item: SettingSection) => item.id;
|
||||
const AnimatedKeyboardAvoidingFlatList = Animated.createAnimatedComponent(
|
||||
@@ -42,13 +43,7 @@ const Group = ({
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: () => {
|
||||
tabBarRef.current?.lock();
|
||||
useNavigationStore.getState().update(
|
||||
{
|
||||
name: "SettingsGroup",
|
||||
title: route.params.name as string
|
||||
},
|
||||
true
|
||||
);
|
||||
useNavigationStore.getState().setFocusedRouteId("Settings");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -62,25 +57,33 @@ const Group = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<DelayLayout type="settings" delay={1}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{route.params.sections ? (
|
||||
<AnimatedKeyboardAvoidingFlatList
|
||||
entering={FadeInDown}
|
||||
data={route.params.sections}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
enableOnAndroid
|
||||
enableAutomaticScroll
|
||||
/>
|
||||
) : null}
|
||||
{route.params.component ? components[route.params.component] : null}
|
||||
</View>
|
||||
</DelayLayout>
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute="Settings"
|
||||
title={route.params.name as string}
|
||||
canGoBack={true}
|
||||
id="Settings"
|
||||
/>
|
||||
<DelayLayout type="settings" delay={1}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{route.params.sections ? (
|
||||
<AnimatedKeyboardAvoidingFlatList
|
||||
entering={FadeInDown}
|
||||
data={route.params.sections}
|
||||
keyExtractor={keyExtractor}
|
||||
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 { RouteParams, SettingSection } from "./types";
|
||||
import SettingsUserSection from "./user-section";
|
||||
import { Header } from "../../components/header";
|
||||
const keyExtractor = (item: SettingSection) => item.id;
|
||||
|
||||
const Home = ({
|
||||
@@ -48,9 +49,7 @@ const Home = ({
|
||||
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: () => {
|
||||
useNavigationStore.getState().update({
|
||||
name: "Settings"
|
||||
});
|
||||
useNavigationStore.getState().setFocusedRouteId("Settings");
|
||||
return false;
|
||||
},
|
||||
focusOnInit: true
|
||||
@@ -74,19 +73,17 @@ const Home = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DelayLayout delay={300} type="settings">
|
||||
{loading && (
|
||||
//@ts-ignore // Migrate to typescript required.
|
||||
<BaseDialog animated={false} bounce={false} visible={true}>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: colors.primary.background,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute="Settings"
|
||||
title="Settings"
|
||||
canGoBack={false}
|
||||
id="Settings"
|
||||
/>
|
||||
<DelayLayout delay={300} type="settings">
|
||||
{loading && (
|
||||
//@ts-ignore // Migrate to typescript required.
|
||||
<BaseDialog animated={false} bounce={false} visible={true}>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -96,45 +93,55 @@ const Home = ({
|
||||
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
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: 100,
|
||||
marginTop: 15
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: colors.primary.background,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
height={5}
|
||||
width={100}
|
||||
animated={true}
|
||||
useNativeDriver
|
||||
indeterminate
|
||||
indeterminateAnimationDuration={2000}
|
||||
unfilledColor={colors.secondary.background}
|
||||
color={colors.primary.accent}
|
||||
borderWidth={0}
|
||||
/>
|
||||
<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
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
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>
|
||||
</BaseDialog>
|
||||
)}
|
||||
</BaseDialog>
|
||||
)}
|
||||
|
||||
<Animated.FlatList
|
||||
entering={FadeInDown}
|
||||
data={settingsGroups}
|
||||
windowSize={1}
|
||||
keyExtractor={keyExtractor}
|
||||
ListFooterComponent={<View style={{ height: 200 }} />}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</DelayLayout>
|
||||
<Animated.FlatList
|
||||
entering={FadeInDown}
|
||||
data={settingsGroups}
|
||||
windowSize={1}
|
||||
keyExtractor={keyExtractor}
|
||||
ListFooterComponent={<View style={{ height: 200 }} />}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</DelayLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -26,34 +26,6 @@ import Home from "./home";
|
||||
import { RouteParams } from "./types";
|
||||
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 = () => {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
@@ -62,7 +34,7 @@ export const Settings = () => {
|
||||
screenListeners={{
|
||||
focus: (e) => {
|
||||
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 { db } from "../../common/database";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useTagStore } from "../../stores/use-tag-store";
|
||||
const prepareSearch = () => {
|
||||
SearchService.update({
|
||||
placeholder: "Search in tags",
|
||||
type: "tags",
|
||||
title: "Tags",
|
||||
get: () => db.tags?.all
|
||||
});
|
||||
};
|
||||
import { db } from "../../common/database";
|
||||
|
||||
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
||||
const tags = useTagStore((state) => state.tags);
|
||||
@@ -44,11 +36,7 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name
|
||||
});
|
||||
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
return !prev?.current;
|
||||
},
|
||||
onBlur: () => false,
|
||||
@@ -56,20 +44,36 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<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."
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
Navigation.push("Search", {
|
||||
placeholder: `Type a keyword to search in ${route.name}`,
|
||||
type: "tag",
|
||||
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 DelayLayout from "../../components/delay-layout";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-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 = () => {
|
||||
presentDialog({
|
||||
@@ -81,40 +73,53 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
|
||||
route.name,
|
||||
Navigation.routeUpdateFunctions[route.name]
|
||||
);
|
||||
useNavigationStore.getState().update({
|
||||
name: route.name
|
||||
});
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
if (
|
||||
!useTrashStore.getState().trash ||
|
||||
useTrashStore.getState().trash?.ids?.length === 0
|
||||
) {
|
||||
useTrashStore.getState().setTrash();
|
||||
}
|
||||
SearchService.prepareSearch = prepareSearch;
|
||||
return false;
|
||||
},
|
||||
onBlur: () => false
|
||||
});
|
||||
|
||||
return (
|
||||
<DelayLayout>
|
||||
<List
|
||||
data={trash}
|
||||
dataType="trash"
|
||||
renderedInRoute="Trash"
|
||||
loading={!isFocused}
|
||||
placeholder={PLACEHOLDER_DATA(db.settings.getTrashCleanupInterval())}
|
||||
headerTitle="Trash"
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={route.name}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
Navigation.push("Search", {
|
||||
placeholder: `Type a keyword to search in ${route.name}`,
|
||||
type: "trash",
|
||||
title: route.name,
|
||||
route: route.name
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{trash && trash?.ids?.length !== 0 ? (
|
||||
<FloatingButton
|
||||
title="Clear all trash"
|
||||
onPress={onPressFloatingButton}
|
||||
alwaysVisible={true}
|
||||
<DelayLayout>
|
||||
<List
|
||||
data={trash}
|
||||
dataType="trash"
|
||||
renderedInRoute="Trash"
|
||||
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 { useFavoriteStore } from "../stores/use-favorite-store";
|
||||
import useNavigationStore, {
|
||||
CurrentScreen,
|
||||
GenericRouteParam,
|
||||
RouteName,
|
||||
RouteParams
|
||||
@@ -34,8 +33,8 @@ import { useTrashStore } from "../stores/use-trash-store";
|
||||
import { eOnNewTopicAdded } from "../utils/events";
|
||||
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import SearchService from "./search";
|
||||
import SettingsService from "./settings";
|
||||
|
||||
/**
|
||||
* Routes that should be updated on focus
|
||||
@@ -113,59 +112,35 @@ function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
|
||||
routesToUpdate?.length > 0
|
||||
? routesToUpdate
|
||||
: (Object.keys(routeNames) as (keyof RouteParams)[]);
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
if (routes.indexOf(currentScreen.name) > -1) {
|
||||
routeUpdateFunctions[currentScreen.name]?.();
|
||||
clearRouteFromQueue(currentScreen.name);
|
||||
const currentRoute = useNavigationStore.getState().currentRoute;
|
||||
if (routes.indexOf(currentRoute) > -1) {
|
||||
routeUpdateFunctions[currentRoute]?.();
|
||||
clearRouteFromQueue(currentRoute);
|
||||
// Remove focused screen from queue
|
||||
routes.splice(routes.indexOf(currentScreen.name), 1);
|
||||
routes.splice(routes.indexOf(currentRoute), 1);
|
||||
}
|
||||
routesUpdateQueue = routesUpdateQueue.concat(routes);
|
||||
routesUpdateQueue = [...new Set(routesUpdateQueue)];
|
||||
}
|
||||
|
||||
function navigate<T extends RouteName>(
|
||||
screen: Omit<Partial<CurrentScreen>, "name"> & {
|
||||
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 navigate<T extends RouteName>(screen: T, params?: RouteParams[T]) {
|
||||
rootNavigatorRef.current?.navigate(screen as any, params);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
rootNavigatorRef.current?.goBack();
|
||||
}
|
||||
|
||||
function push<T extends RouteName>(
|
||||
screen: CurrentScreen,
|
||||
params: RouteParams[T]
|
||||
) {
|
||||
useNavigationStore.getState().update(screen, !!params?.canGoBack);
|
||||
rootNavigatorRef.current?.dispatch(StackActions.push(screen.name, params));
|
||||
function push<T extends RouteName>(screen: T, params: RouteParams[T]) {
|
||||
rootNavigatorRef.current?.dispatch(StackActions.push(screen as any, params));
|
||||
}
|
||||
|
||||
function replace<T extends RouteName>(
|
||||
screen: CurrentScreen,
|
||||
params: RouteParams[T]
|
||||
) {
|
||||
useNavigationStore.getState().update(screen, !!params?.canGoBack);
|
||||
rootNavigatorRef.current?.dispatch(StackActions.replace(screen.name, params));
|
||||
function replace<T extends RouteName>(screen: T, params: RouteParams[T]) {
|
||||
rootNavigatorRef.current?.dispatch(StackActions.replace(screen, params));
|
||||
}
|
||||
|
||||
function popToTop() {
|
||||
rootNavigatorRef.current?.dispatch(StackActions.popToTop());
|
||||
useNavigationStore.getState().update({
|
||||
name: (SettingsService.get().homepage as RouteName) || "Notes"
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
Color,
|
||||
ItemType,
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
Tag,
|
||||
Topic,
|
||||
TrashItem
|
||||
} from "@notesnook/core/dist/types";
|
||||
import create, { State } from "zustand";
|
||||
import { ColorValues } from "../utils/colors";
|
||||
|
||||
export type GenericRouteParam = { [name: string]: unknown };
|
||||
export type GenericRouteParam = undefined;
|
||||
|
||||
export type NotebookScreenParams = {
|
||||
item: Notebook;
|
||||
@@ -38,7 +38,7 @@ export type NotebookScreenParams = {
|
||||
};
|
||||
|
||||
export type NotesScreenParams = {
|
||||
item: Note | Notebook | Topic | Tag | Color | TrashItem | Reminder;
|
||||
item: Note | Notebook | Tag | Color | TrashItem | Reminder;
|
||||
title: string;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
@@ -56,90 +56,64 @@ export type AuthParams = {
|
||||
|
||||
export type RouteParams = {
|
||||
Notes: GenericRouteParam;
|
||||
Notebooks: GenericRouteParam;
|
||||
Notebooks: {
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
Notebook: NotebookScreenParams;
|
||||
NotesPage: NotesScreenParams;
|
||||
Tags: GenericRouteParam;
|
||||
Favorites: GenericRouteParam;
|
||||
Trash: GenericRouteParam;
|
||||
Search: GenericRouteParam;
|
||||
Search: {
|
||||
placeholder: string;
|
||||
type: ItemType;
|
||||
title: string;
|
||||
route: RouteName;
|
||||
ids?: string[];
|
||||
};
|
||||
Settings: GenericRouteParam;
|
||||
TaggedNotes: NotesScreenParams;
|
||||
ColoredNotes: NotesScreenParams;
|
||||
TopicNotes: NotesScreenParams;
|
||||
Monographs: NotesScreenParams;
|
||||
AppLock: AppLockRouteParams;
|
||||
Auth: AuthParams;
|
||||
Reminders: GenericRouteParam;
|
||||
SettingsGroup: GenericRouteParam;
|
||||
};
|
||||
|
||||
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 = {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
interface NavigationStore extends State {
|
||||
currentScreen: CurrentScreen;
|
||||
currentScreenRaw: Partial<CurrentScreen>;
|
||||
currentRoute: RouteName;
|
||||
canGoBack?: boolean;
|
||||
update: (
|
||||
currentScreen: Omit<Partial<CurrentScreen>, "name"> & {
|
||||
name: keyof RouteParams;
|
||||
},
|
||||
canGoBack?: boolean,
|
||||
headerRightButtons?: HeaderRightButton[]
|
||||
) => void;
|
||||
focusedRouteId?: string;
|
||||
update: (currentScreen: RouteName) => void;
|
||||
headerRightButtons?: HeaderRightButton[];
|
||||
buttonAction: () => void;
|
||||
setButtonAction: (buttonAction: () => void) => void;
|
||||
setFocusedRouteId: (id?: string) => void;
|
||||
}
|
||||
|
||||
const useNavigationStore = create<NavigationStore>((set, get) => ({
|
||||
currentScreen: {
|
||||
name: "Notes",
|
||||
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;
|
||||
focusedRouteId: "Notes",
|
||||
setFocusedRouteId: (id) => {
|
||||
set({
|
||||
currentScreen: {
|
||||
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
|
||||
focusedRouteId: id
|
||||
});
|
||||
console.log("CurrentRoute ID", id);
|
||||
},
|
||||
currentRoute: "Notes",
|
||||
canGoBack: false,
|
||||
update: (currentScreen) => {
|
||||
set({
|
||||
currentRoute: currentScreen
|
||||
});
|
||||
console.log("CurrentRoute", currentScreen);
|
||||
},
|
||||
headerRightButtons: [],
|
||||
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/toolbar": "^3.12.0",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.68",
|
||||
"@theme-ui/color": "^0.14.7",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
@@ -1439,7 +1440,6 @@
|
||||
"react-modal": "3.13.1",
|
||||
"react-qrcode-logo": "^2.2.1",
|
||||
"react-scroll-sync": "^0.9.0",
|
||||
"react-virtuoso": "^4.4.2",
|
||||
"timeago.js": "4.0.2",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"w3c-keyname": "^2.2.6",
|
||||
|
||||
Reference in New Issue
Block a user