mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 20:20:21 +01:00
mobile: use index based grouping
This commit is contained in:
@@ -18,22 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useDBItem } from "../../hooks/use-db-item";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import Actions from "./actions";
|
||||
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useDBItem } from "../../hooks/use-db-item";
|
||||
|
||||
function getFileExtension(filename: string) {
|
||||
var ext = /^.+\.([^.]+)$/.exec(filename);
|
||||
const ext = /^.+\.([^.]+)$/.exec(filename);
|
||||
return ext == null ? "" : ext[1];
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const AttachmentItem = ({
|
||||
hideWhenNotDownloading,
|
||||
context
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
attachments?: VirtualizedGrouping<Attachment>;
|
||||
encryption?: boolean;
|
||||
setAttachments: (attachments: any) => void;
|
||||
@@ -54,7 +54,7 @@ export const AttachmentItem = ({
|
||||
hideWhenNotDownloading?: boolean;
|
||||
context?: string;
|
||||
}) => {
|
||||
const [attachment] = useDBItem(id, "attachment", attachments?.item);
|
||||
const [attachment] = useDBItem(id, "attachment", attachments);
|
||||
|
||||
const { colors } = useThemeColors();
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||
@@ -68,7 +68,7 @@ export const AttachmentItem = ({
|
||||
};
|
||||
|
||||
return (hideWhenNotDownloading &&
|
||||
(!currentProgress || !(currentProgress as any).value)) ||
|
||||
(!currentProgress || !currentProgress.value)) ||
|
||||
!attachment ? null : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
@@ -133,8 +133,8 @@ export const AttachmentItem = ({
|
||||
{!hideWhenNotDownloading ? (
|
||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||
{formatBytes(attachment.size)}{" "}
|
||||
{(currentProgress as any)?.type
|
||||
? "(" + (currentProgress as any).type + "ing - tap to cancel)"
|
||||
{currentProgress?.type
|
||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||
: ""}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
@@ -91,7 +91,7 @@ const DownloadAttachments = ({
|
||||
|
||||
const successResults = () => {
|
||||
const results = [];
|
||||
for (let [key, value] of result.entries()) {
|
||||
for (const [key, value] of result.entries()) {
|
||||
if (value.status === 1) results.push(db.attachments.attachment(key));
|
||||
}
|
||||
return results;
|
||||
@@ -99,7 +99,7 @@ const DownloadAttachments = ({
|
||||
|
||||
const failedResults = () => {
|
||||
const results = [];
|
||||
for (let [key, value] of result.entries()) {
|
||||
for (const [key, value] of result.entries()) {
|
||||
if (value.status === 0) results.push(db.attachments.attachment(key));
|
||||
}
|
||||
return results;
|
||||
@@ -207,11 +207,11 @@ const DownloadAttachments = ({
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
keyExtractor={(item) => item as string}
|
||||
renderItem={({ item }) => {
|
||||
keyExtractor={(item, index) => "attachment" + index}
|
||||
renderItem={({ index }) => {
|
||||
return (
|
||||
<AttachmentItem
|
||||
id={item as string}
|
||||
id={index}
|
||||
setAttachments={() => {}}
|
||||
pressable={false}
|
||||
hideWhenNotDownloading={true}
|
||||
|
||||
@@ -83,14 +83,14 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||
}
|
||||
clearTimeout(searchTimer.current);
|
||||
searchTimer.current = setTimeout(async () => {
|
||||
let results = await db.lookup.attachments(
|
||||
attachmentSearchValue.current as string
|
||||
);
|
||||
const results = await db.lookup
|
||||
.attachments(attachmentSearchValue.current as string)
|
||||
.sorted();
|
||||
setAttachments(results);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: string }) => (
|
||||
const renderItem = ({ item }: { item: string | number }) => (
|
||||
<AttachmentItem
|
||||
setAttachments={setAttachments}
|
||||
attachments={attachments}
|
||||
@@ -102,15 +102,15 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||
const onCheck = async () => {
|
||||
if (!attachments) return;
|
||||
setLoading(true);
|
||||
for (let id of attachments.ids) {
|
||||
const attachment = await attachments.item(id as string);
|
||||
for (const id of attachments.ids) {
|
||||
const attachment = (await attachments.item(id))?.item;
|
||||
if (!attachment) continue;
|
||||
|
||||
let result = await filesystem.checkAttachment(attachment.hash);
|
||||
const result = await filesystem.checkAttachment(attachment.hash);
|
||||
if (result.failed) {
|
||||
await db.attachments.markAsFailed(attachment.hash, result.failed);
|
||||
} else {
|
||||
await db.attachments.markAsFailed(id as string, undefined);
|
||||
await db.attachments.markAsFailed(attachment.id, undefined);
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
@@ -306,7 +306,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||
/>
|
||||
}
|
||||
estimatedItemSize={50}
|
||||
data={attachments?.ids as string[]}
|
||||
data={attachments?.ids}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
|
||||
@@ -51,19 +51,18 @@ const JumpToSectionDialog = () => {
|
||||
const notes = data;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const currentScrollPosition = useRef(0);
|
||||
const [groups, setGroups] = useState<
|
||||
{
|
||||
index: number;
|
||||
group: GroupHeader;
|
||||
}[]
|
||||
>();
|
||||
const offsets = useRef<number[]>([]);
|
||||
const timeout = useRef<NodeJS.Timeout>();
|
||||
|
||||
const onPress = (item: GroupHeader) => {
|
||||
const index = notes?.ids?.findIndex((i) => {
|
||||
if (typeof i === "object") {
|
||||
return i.title === item.title && i.type === "header";
|
||||
} else {
|
||||
false;
|
||||
}
|
||||
});
|
||||
const onPress = (item: { index: number; group: GroupHeader }) => {
|
||||
scrollRef.current?.current?.scrollToIndex({
|
||||
index: index as number,
|
||||
index: item.index,
|
||||
animated: true
|
||||
});
|
||||
close();
|
||||
@@ -97,55 +96,44 @@ const JumpToSectionDialog = () => {
|
||||
}, [open]);
|
||||
|
||||
const onScroll = (data: { x: number; y: number }) => {
|
||||
const y = data.y;
|
||||
if (timeout) {
|
||||
clearTimeout(timeout.current);
|
||||
timeout.current = undefined;
|
||||
}
|
||||
timeout.current = setTimeout(() => {
|
||||
setCurrentIndex(
|
||||
offsets.current?.findIndex(
|
||||
(o, i) => o <= y && offsets.current[i + 1] > y
|
||||
) || 0
|
||||
);
|
||||
}, 200);
|
||||
currentScrollPosition.current = data.y;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const loadOffsets = useCallback(() => {
|
||||
notes?.ids
|
||||
.filter((i) => typeof i === "object" && i.type === "header")
|
||||
.map((item, index) => {
|
||||
if (typeof item === "string") return;
|
||||
|
||||
const loadGroupsAndOffsets = useCallback(() => {
|
||||
notes?.groups?.().then((groups) => {
|
||||
setGroups(groups);
|
||||
offsets.current = [];
|
||||
groups.map((item, index) => {
|
||||
let offset = 35 * index;
|
||||
let ind = notes.ids.findIndex(
|
||||
(i) =>
|
||||
typeof i === "object" &&
|
||||
i.title === item.title &&
|
||||
i.type === "header"
|
||||
);
|
||||
let groupIndex = item.index;
|
||||
const messageState = useMessageStore.getState().message;
|
||||
const msgOffset = messageState?.visible ? 60 : 10;
|
||||
|
||||
ind = ind + 1;
|
||||
ind = ind - (index + 1);
|
||||
offset = offset + ind * 100 + msgOffset;
|
||||
groupIndex = groupIndex + 1;
|
||||
groupIndex = groupIndex - (index + 1);
|
||||
offset = offset + groupIndex * 100 + msgOffset;
|
||||
offsets.current.push(offset);
|
||||
});
|
||||
}, [notes]);
|
||||
|
||||
useEffect(() => {
|
||||
loadOffsets();
|
||||
}, [loadOffsets, notes]);
|
||||
const index = offsets.current?.findIndex((o, i) => {
|
||||
return (
|
||||
o <= currentScrollPosition.current + 100 &&
|
||||
offsets.current[i + 1] - 100 > currentScrollPosition.current
|
||||
);
|
||||
});
|
||||
|
||||
setCurrentIndex(index < 0 ? 0 : index);
|
||||
});
|
||||
}, [notes]);
|
||||
|
||||
return !visible ? null : (
|
||||
<BaseDialog
|
||||
onShow={() => {
|
||||
loadOffsets();
|
||||
loadGroupsAndOffsets();
|
||||
}}
|
||||
onRequestClose={close}
|
||||
visible={true}
|
||||
@@ -178,40 +166,38 @@ const JumpToSectionDialog = () => {
|
||||
paddingBottom: 20
|
||||
}}
|
||||
>
|
||||
{notes?.ids
|
||||
.filter((i) => typeof i === "object" && i.type === "header")
|
||||
.map((item, index) => {
|
||||
return typeof item === "object" && item.title ? (
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
onPress={() => onPress(item)}
|
||||
type={currentIndex === index ? "selected" : "transparent"}
|
||||
customStyle={{
|
||||
minWidth: "20%",
|
||||
width: null,
|
||||
paddingHorizontal: 12,
|
||||
margin: 5,
|
||||
borderRadius: 100,
|
||||
height: 25,
|
||||
marginVertical: 10
|
||||
{groups?.map((item, index) => {
|
||||
return (
|
||||
<PressableButton
|
||||
key={item.group.id}
|
||||
onPress={() => onPress(item)}
|
||||
type={currentIndex === index ? "selected" : "transparent"}
|
||||
customStyle={{
|
||||
minWidth: "20%",
|
||||
width: null,
|
||||
paddingHorizontal: 12,
|
||||
margin: 5,
|
||||
borderRadius: 100,
|
||||
height: 25,
|
||||
marginVertical: 10
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
color={
|
||||
currentIndex === index
|
||||
? colors.selected.accent
|
||||
: colors.primary.accent
|
||||
}
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
color={
|
||||
currentIndex === index
|
||||
? colors.selected.accent
|
||||
: colors.primary.accent
|
||||
}
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
) : null;
|
||||
})}
|
||||
{item.group.title}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -17,13 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
GroupHeader,
|
||||
GroupingKey,
|
||||
Item,
|
||||
VirtualizedGrouping,
|
||||
isGroupHeader
|
||||
} from "@notesnook/core";
|
||||
import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
@@ -39,11 +33,10 @@ import { eSendEvent } from "../../services/event-manager";
|
||||
import Sync from "../../services/sync";
|
||||
import { RouteName } from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { eOpenJumpToDialog, eScrollEvent } from "../../utils/events";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { Footer } from "../list-items/footer";
|
||||
import { Header } from "../list-items/headers/header";
|
||||
import { SectionHeader } from "../list-items/headers/section-header";
|
||||
import { Empty, PlaceholderData } from "./empty";
|
||||
import { ListItemWrapper } from "./list-item.wrapper";
|
||||
|
||||
@@ -92,35 +85,20 @@ export default function List(props: ListProps) {
|
||||
};
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ item, index }: { item: string | GroupHeader; index: number }) => {
|
||||
if (isGroupHeader(item)) {
|
||||
return (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={item}
|
||||
index={index}
|
||||
dataType={props.dataType}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: scrollRef,
|
||||
data: props.data
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ListItemWrapper
|
||||
id={item}
|
||||
index={index}
|
||||
isSheet={props.isRenderedInActionSheet || false}
|
||||
items={props.data}
|
||||
group={groupType as GroupingKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
({ index }: { index: number }) => {
|
||||
return (
|
||||
<ListItemWrapper
|
||||
index={index}
|
||||
isSheet={props.isRenderedInActionSheet || false}
|
||||
items={props.data}
|
||||
groupOptions={groupOptions}
|
||||
group={groupType as GroupingKey}
|
||||
renderedInRoute={props.renderedInRoute}
|
||||
customAccentColor={props.customAccentColor}
|
||||
dataType={props.dataType}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[
|
||||
groupOptions,
|
||||
@@ -181,7 +159,6 @@ export default function List(props: ListProps) {
|
||||
onMomentumScrollEnd={() => {
|
||||
tabBarRef.current?.unlock();
|
||||
}}
|
||||
getItemType={(item: any) => (isGroupHeader(item) ? "header" : "item")}
|
||||
estimatedItemSize={isCompactModeEnabled ? 60 : 100}
|
||||
directionalLockEnabled={true}
|
||||
keyboardShouldPersistTaps="always"
|
||||
|
||||
@@ -16,24 +16,29 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from "react";
|
||||
import { getSortValue } from "@notesnook/core/dist/utils/grouping";
|
||||
import {
|
||||
Color,
|
||||
GroupHeader,
|
||||
GroupOptions,
|
||||
GroupingKey,
|
||||
Item,
|
||||
VirtualizedGrouping,
|
||||
Color,
|
||||
Reminder,
|
||||
ItemType,
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
Tag,
|
||||
TrashItem,
|
||||
ItemType,
|
||||
Note
|
||||
VirtualizedGrouping
|
||||
} from "@notesnook/core";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getSortValue } from "@notesnook/core/dist/utils/grouping";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { db } from "../../common/database";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { RouteName } from "../../stores/use-navigation-store";
|
||||
import { eOpenJumpToDialog } from "../../utils/events";
|
||||
import { SectionHeader } from "../list-items/headers/section-header";
|
||||
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import TagItem from "../list-items/tag";
|
||||
@@ -45,13 +50,17 @@ export type TagsWithDateEdited = WithDateEdited<Tag>;
|
||||
type ListItemWrapperProps<TItem = Item> = {
|
||||
group?: GroupingKey;
|
||||
items: VirtualizedGrouping<TItem> | undefined;
|
||||
id: string;
|
||||
isSheet: boolean;
|
||||
index: number;
|
||||
renderedInRoute?: RouteName;
|
||||
customAccentColor?: string;
|
||||
dataType: string;
|
||||
scrollRef: any;
|
||||
groupOptions: GroupOptions;
|
||||
};
|
||||
|
||||
export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
const { id, items, group, isSheet, index } = props;
|
||||
const { items, group, isSheet, index, groupOptions } = props;
|
||||
const [item, setItem] = useState<Item>();
|
||||
const tags = useRef<TagsWithDateEdited>();
|
||||
const notebooks = useRef<NotebooksWithDateEdited>();
|
||||
@@ -59,25 +68,34 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
const color = useRef<Color>();
|
||||
const totalNotes = useRef<number>(0);
|
||||
const attachmentsCount = useRef(0);
|
||||
const [groupHeader, setGroupHeader] = useState<GroupHeader>();
|
||||
const previousIndex = useRef<number>();
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const { item, data } = (await items?.item(id, resolveItems)) || {};
|
||||
if (!item) return;
|
||||
if (item.type === "note" && isNoteResolvedData(data)) {
|
||||
tags.current = data.tags;
|
||||
notebooks.current = data.notebooks;
|
||||
reminder.current = data.reminder;
|
||||
color.current = data.color;
|
||||
attachmentsCount.current = data.attachmentsCount;
|
||||
} else if (item.type === "notebook" && typeof data === "number") {
|
||||
totalNotes.current = data;
|
||||
} else if (item.type === "tag" && typeof data === "number") {
|
||||
totalNotes.current = data;
|
||||
try {
|
||||
const { item, data, group } =
|
||||
(await items?.item(index, resolveItems)) || {};
|
||||
if (!item) return;
|
||||
if (item.type === "note" && isNoteResolvedData(data)) {
|
||||
tags.current = data.tags;
|
||||
notebooks.current = data.notebooks;
|
||||
reminder.current = data.reminder;
|
||||
color.current = data.color;
|
||||
attachmentsCount.current = data.attachmentsCount;
|
||||
} else if (item.type === "notebook" && typeof data === "number") {
|
||||
totalNotes.current = data;
|
||||
} else if (item.type === "tag" && typeof data === "number") {
|
||||
totalNotes.current = data;
|
||||
}
|
||||
previousIndex.current = index;
|
||||
setItem(item);
|
||||
setGroupHeader(group);
|
||||
} catch (e) {
|
||||
console.log("Error", e);
|
||||
}
|
||||
setItem(item);
|
||||
})();
|
||||
}, [id, items]);
|
||||
}, [index, items]);
|
||||
|
||||
if (!item) return <View style={{ height: 100, width: "100%" }} />;
|
||||
|
||||
@@ -85,40 +103,117 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
switch (type) {
|
||||
case "note": {
|
||||
return (
|
||||
<NoteWrapper
|
||||
item={item as Note}
|
||||
tags={tags.current}
|
||||
color={color.current}
|
||||
notebooks={notebooks.current}
|
||||
reminder={reminder.current}
|
||||
attachmentsCount={attachmentsCount.current}
|
||||
date={getDate(item, group)}
|
||||
isRenderedInActionSheet={isSheet}
|
||||
index={index}
|
||||
/>
|
||||
<>
|
||||
{groupHeader && previousIndex.current === index ? (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={groupHeader}
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: props.scrollRef,
|
||||
data: items
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<NoteWrapper
|
||||
item={item as Note}
|
||||
tags={tags.current}
|
||||
color={color.current}
|
||||
notebooks={notebooks.current}
|
||||
reminder={reminder.current}
|
||||
attachmentsCount={attachmentsCount.current}
|
||||
date={getDate(item, group)}
|
||||
isRenderedInActionSheet={isSheet}
|
||||
index={index}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "notebook":
|
||||
return (
|
||||
<NotebookWrapper
|
||||
item={item as Notebook}
|
||||
totalNotes={totalNotes.current}
|
||||
date={getDate(item, group)}
|
||||
index={index}
|
||||
/>
|
||||
<>
|
||||
{groupHeader && previousIndex.current === index ? (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={groupHeader}
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: props.scrollRef,
|
||||
data: items
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<NotebookWrapper
|
||||
item={item as Notebook}
|
||||
totalNotes={totalNotes.current}
|
||||
date={getDate(item, group)}
|
||||
index={index}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
case "reminder":
|
||||
return (
|
||||
<ReminderItem item={item as Reminder} index={index} isSheet={isSheet} />
|
||||
<>
|
||||
{groupHeader && previousIndex.current === index ? (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={groupHeader}
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: props.scrollRef,
|
||||
data: items
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<ReminderItem
|
||||
item={item as Reminder}
|
||||
index={index}
|
||||
isSheet={isSheet}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
case "tag":
|
||||
return (
|
||||
<TagItem
|
||||
item={item as Tag}
|
||||
index={index}
|
||||
totalNotes={totalNotes.current}
|
||||
/>
|
||||
<>
|
||||
{groupHeader && previousIndex.current === index ? (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={groupHeader}
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: props.scrollRef,
|
||||
data: items
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<TagItem
|
||||
item={item as Tag}
|
||||
index={index}
|
||||
totalNotes={totalNotes.current}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
@@ -136,6 +231,19 @@ function withDateEdited<
|
||||
return { dateEdited: latestDateEdited, items };
|
||||
}
|
||||
|
||||
export async function resolveItems(ids: string[], items: Item[]) {
|
||||
const { type } = items[0];
|
||||
if (type === "note") return resolveNotes(ids);
|
||||
else if (type === "notebook") {
|
||||
return Promise.all(ids.map((id) => db.notebooks.totalNotes(id)));
|
||||
} else if (type === "tag") {
|
||||
return Promise.all(
|
||||
ids.map((id) => db.relations.from({ id, type: "tag" }, "note").count())
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getDate(item: Item, groupType?: GroupingKey): number {
|
||||
return (
|
||||
getSortValue(
|
||||
@@ -151,22 +259,6 @@ function getDate(item: Item, groupType?: GroupingKey): number {
|
||||
);
|
||||
}
|
||||
|
||||
async function resolveItems(ids: string[], items: Record<string, Item>) {
|
||||
const { type } = items[ids[0]];
|
||||
if (type === "note") return resolveNotes(ids);
|
||||
else if (type === "notebook") {
|
||||
const data: Record<string, number> = {};
|
||||
for (const id of ids) data[id] = await db.notebooks.totalNotes(id);
|
||||
return data;
|
||||
} else if (type === "tag") {
|
||||
const data: Record<string, number> = {};
|
||||
for (const id of ids)
|
||||
data[id] = await db.relations.from({ id, type: "tag" }, "note").count();
|
||||
return data;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
type NoteResolvedData = {
|
||||
notebooks?: NotebooksWithDateEdited;
|
||||
reminder?: Reminder;
|
||||
@@ -183,7 +275,6 @@ async function resolveNotes(ids: string[]) {
|
||||
...(await db.relations.from({ type: "note", ids }, "reminder").get())
|
||||
];
|
||||
console.timeEnd("relations");
|
||||
|
||||
const relationIds: {
|
||||
notebooks: Set<string>;
|
||||
colors: Set<string>;
|
||||
@@ -207,15 +298,15 @@ async function resolveNotes(ids: string[]) {
|
||||
> = {};
|
||||
for (const relation of relations) {
|
||||
const noteId =
|
||||
relation.toType === "relation" ? relation.fromId : relation.toId;
|
||||
relation.toType === "reminder" ? relation.fromId : relation.toId;
|
||||
const data = grouped[noteId] || {
|
||||
notebooks: [],
|
||||
tags: []
|
||||
};
|
||||
|
||||
if (relation.toType === "relation" && !data.reminder) {
|
||||
data.reminder = relation.fromId;
|
||||
relationIds.reminders.add(relation.fromId);
|
||||
if (relation.toType === "reminder" && !data.reminder) {
|
||||
data.reminder = relation.toId;
|
||||
relationIds.reminders.add(relation.toId);
|
||||
} else if (relation.fromType === "notebook" && data.notebooks.length < 2) {
|
||||
data.notebooks.push(relation.fromId);
|
||||
relationIds.notebooks.add(relation.fromId);
|
||||
@@ -226,7 +317,7 @@ async function resolveNotes(ids: string[]) {
|
||||
data.color = relation.fromId;
|
||||
relationIds.colors.add(relation.fromId);
|
||||
}
|
||||
grouped[relation.toId] = data;
|
||||
grouped[noteId] = data;
|
||||
}
|
||||
|
||||
console.time("resolve");
|
||||
@@ -240,10 +331,10 @@ async function resolveNotes(ids: string[]) {
|
||||
};
|
||||
console.timeEnd("resolve");
|
||||
|
||||
const data: Record<string, NoteResolvedData> = {};
|
||||
const data: NoteResolvedData[] = [];
|
||||
for (const noteId in grouped) {
|
||||
const group = grouped[noteId];
|
||||
data[noteId] = {
|
||||
data.push({
|
||||
color: group.color ? resolved.colors[group.color] : undefined,
|
||||
reminder: group.reminder ? resolved.reminders[group.reminder] : undefined,
|
||||
tags: withDateEdited(group.tags.map((id) => resolved.tags[id])),
|
||||
@@ -252,12 +343,12 @@ async function resolveNotes(ids: string[]) {
|
||||
),
|
||||
attachmentsCount:
|
||||
(await db.attachments?.ofNote(noteId, "all").ids())?.length || 0
|
||||
};
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function isNoteResolvedData(data: unknown): data is NoteResolvedData {
|
||||
export function isNoteResolvedData(data: unknown): data is NoteResolvedData {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
!!data &&
|
||||
|
||||
@@ -120,13 +120,15 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
<Line bottom={0} />
|
||||
{item.type === "note" ? <Tags close={close} item={item} /> : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Notebooks note={item} close={close} />
|
||||
</View>
|
||||
{item.type === "note" ? (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Notebooks note={item} close={close} />
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<Items
|
||||
item={item}
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { GroupHeader, Note } from "@notesnook/core";
|
||||
import { Note } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useCallback, useEffect } from "react";
|
||||
import { Keyboard, TouchableOpacity, View } from "react-native";
|
||||
@@ -135,10 +135,9 @@ const MoveNoteSheet = ({
|
||||
};
|
||||
|
||||
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} />
|
||||
),
|
||||
({ item, index }: { item: string | number; index: number }) => (
|
||||
<NotebookItem items={notebooks} id={item as string} index={index} />
|
||||
),
|
||||
[notebooks]
|
||||
);
|
||||
|
||||
@@ -220,12 +219,11 @@ const MoveNoteSheet = ({
|
||||
}}
|
||||
>
|
||||
<FlashList
|
||||
data={notebooks?.ids?.filter((id) => typeof id === "string")}
|
||||
data={notebooks?.ids}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
estimatedItemSize={50}
|
||||
keyExtractor={(item) => item as string}
|
||||
renderItem={renderNotebook}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
|
||||
@@ -18,7 +18,7 @@ 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 React, { useEffect } from "react";
|
||||
import { View, useWindowDimensions } from "react-native";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useTotalNotes } from "../../../hooks/use-db-item";
|
||||
@@ -46,31 +46,39 @@ export const NotebookItem = ({
|
||||
parent,
|
||||
items
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
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 expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||
const { nestedNotebooks, notebook: item } = useNotebook(id, items, expanded);
|
||||
const { totalNotes: totalNotes, getTotalNotes } = useTotalNotes("notebook");
|
||||
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const selection = useNotebookItemSelectionStore((state) =>
|
||||
id ? state.selection[id] : undefined
|
||||
item?.id ? state.selection[item?.id] : undefined
|
||||
);
|
||||
const isSelected = selection === "selected";
|
||||
const isFocused = focusedRouteId === id;
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (item?.id) {
|
||||
getTotalNotes([item?.id]);
|
||||
}
|
||||
}, [getTotalNotes, item?.id]);
|
||||
|
||||
const onPress = () => {
|
||||
if (!item) return;
|
||||
const state = useNotebookItemSelectionStore.getState();
|
||||
|
||||
if (isSelected) {
|
||||
state.markAs(item, !state.initialState[id] ? undefined : "deselected");
|
||||
state.markAs(
|
||||
item,
|
||||
!state.initialState[item?.id] ? undefined : "deselected"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +120,7 @@ export const NotebookItem = ({
|
||||
item,
|
||||
!isSelected
|
||||
? "selected"
|
||||
: !state.initialState[id]
|
||||
: !state.initialState[item?.id]
|
||||
? undefined
|
||||
: "deselected"
|
||||
);
|
||||
@@ -141,7 +149,8 @@ export const NotebookItem = ({
|
||||
size={SIZE.xl}
|
||||
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||
onPress={() => {
|
||||
useNotebookExpandedStore.getState().setExpanded(id);
|
||||
if (!item?.id) return;
|
||||
useNotebookExpandedStore.getState().setExpanded(item?.id);
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
@@ -201,9 +210,9 @@ export const NotebookItem = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{totalNotes?.(id) ? (
|
||||
{item?.id && totalNotes?.(item?.id) ? (
|
||||
<Paragraph size={SIZE.sm} color={colors.secondary.paragraph}>
|
||||
{totalNotes(id)}
|
||||
{totalNotes(item?.id)}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
<IconButton
|
||||
@@ -231,8 +240,8 @@ export const NotebookItem = ({
|
||||
? null
|
||||
: nestedNotebooks?.ids.map((id, index) => (
|
||||
<NotebookItem
|
||||
key={id as string}
|
||||
id={id as string}
|
||||
key={item?.id + "_" + id}
|
||||
id={id}
|
||||
index={index}
|
||||
currentLevel={currentLevel + 1}
|
||||
items={nestedNotebooks}
|
||||
|
||||
@@ -115,7 +115,7 @@ const ManageTagsSheet = (props: {
|
||||
});
|
||||
} else {
|
||||
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
|
||||
console.log("items loaded tags");
|
||||
console.log("items loaded tags", items.ids);
|
||||
setTags(items);
|
||||
});
|
||||
}
|
||||
@@ -237,11 +237,10 @@ const ManageTagsSheet = (props: {
|
||||
);
|
||||
|
||||
const renderTag = useCallback(
|
||||
({ item }: { item: string; index: number }) => (
|
||||
({ item, index }: { item: string | number; index: number }) => (
|
||||
<TagItem
|
||||
key={item as string}
|
||||
tags={tags as VirtualizedGrouping<Tag>}
|
||||
id={item as string}
|
||||
id={index}
|
||||
onPress={onPress}
|
||||
/>
|
||||
),
|
||||
@@ -303,13 +302,13 @@ const ManageTagsSheet = (props: {
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
data={tags?.ids?.filter((id) => typeof id === "string") as string[]}
|
||||
data={tags?.ids}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
keyboardShouldPersistTaps
|
||||
keyboardDismissMode="interactive"
|
||||
keyExtractor={(item) => item as string}
|
||||
keyExtractor={(item) => item + "_tag"}
|
||||
renderItem={renderTag}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
@@ -352,29 +351,38 @@ const TagItem = ({
|
||||
tags,
|
||||
onPress
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
tags: VirtualizedGrouping<Tag>;
|
||||
onPress: (id: string) => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [tag] = useDBItem(id, "tag", tags);
|
||||
const selection = useTagItemSelection((state) => state.selection[id]);
|
||||
const selection = useTagItemSelection((state) =>
|
||||
tag?.id ? state.selection[tag?.id] : false
|
||||
);
|
||||
|
||||
return (
|
||||
return !tag ? null : (
|
||||
<PressableButton
|
||||
key={tag?.id}
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "flex-start",
|
||||
height: 40
|
||||
}}
|
||||
onPress={() => onPress(id)}
|
||||
onPress={() => {
|
||||
if (!tag) return;
|
||||
onPress(tag.id);
|
||||
}}
|
||||
type="gray"
|
||||
>
|
||||
{!tag ? null : (
|
||||
<Icon
|
||||
size={22}
|
||||
onPress={() => onPress(id)}
|
||||
onPress={() => {
|
||||
if (!tag) return;
|
||||
onPress(tag.id);
|
||||
}}
|
||||
color={
|
||||
selection === "selected" || selection === "intermediate"
|
||||
? colors.selected.icon
|
||||
@@ -406,7 +414,6 @@ const TagItem = ({
|
||||
style={{
|
||||
width: 200,
|
||||
height: 30,
|
||||
// backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -36,6 +36,7 @@ import { IconButton } from "../../ui/icon-button";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useDBItem } from "../../../hooks/use-db-item";
|
||||
|
||||
export const MoveNotes = ({
|
||||
notebook,
|
||||
@@ -45,11 +46,13 @@ export const MoveNotes = ({
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [currentNotebook, setCurrentNotebook] = useState(notebook);
|
||||
const currentNotebook = notebook;
|
||||
|
||||
const { height } = useWindowDimensions();
|
||||
const [selectedNoteIds, setSelectedNoteIds] = useState<string[]>([]);
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
const [existingNoteIds, setExistingNoteIds] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
|
||||
setNotes(notes);
|
||||
@@ -85,17 +88,18 @@ export const MoveNotes = ({
|
||||
);
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ item }: { item: string }) => {
|
||||
({ index }: { item: string | number; index: number }) => {
|
||||
return (
|
||||
<SelectableNoteItem
|
||||
id={item}
|
||||
id={index}
|
||||
items={notes}
|
||||
select={select}
|
||||
selected={selectedNoteIds?.indexOf(item) > -1}
|
||||
selected={(id) => selectedNoteIds?.indexOf(id) > -1}
|
||||
exists={(id) => existingNoteIds.indexOf(id) > -1}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[notes, select, selectedNoteIds]
|
||||
[existingNoteIds, notes, select, selectedNoteIds]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -128,9 +132,7 @@ export const MoveNotes = ({
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
data={(notes?.ids as string[])?.filter(
|
||||
(id) => existingNoteIds?.indexOf(id) === -1
|
||||
)}
|
||||
data={notes?.ids}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
{selectedNoteIds.length > 0 ? (
|
||||
@@ -157,21 +159,19 @@ const SelectableNoteItem = ({
|
||||
id,
|
||||
items,
|
||||
select,
|
||||
selected
|
||||
selected,
|
||||
exists
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
items?: VirtualizedGrouping<Note>;
|
||||
select: (id: string) => void;
|
||||
selected?: boolean;
|
||||
selected?: (id: string) => boolean;
|
||||
exists: (id: string) => boolean;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [item, setItem] = useState<Note>();
|
||||
const [item] = useDBItem(id, "note", items);
|
||||
|
||||
useEffect(() => {
|
||||
items?.item(id).then((item) => setItem(item));
|
||||
}, [id, items]);
|
||||
|
||||
return !item ? null : (
|
||||
return !item || exists(item.id) ? null : (
|
||||
<PressableButton
|
||||
testID="listitem.select"
|
||||
onPress={() => {
|
||||
@@ -197,10 +197,14 @@ const SelectableNoteItem = ({
|
||||
select(item?.id);
|
||||
}}
|
||||
name={
|
||||
selected ? "check-circle-outline" : "checkbox-blank-circle-outline"
|
||||
selected?.(item?.id)
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
type="selected"
|
||||
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||
color={
|
||||
selected?.(item?.id) ? colors.selected.icon : colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
|
||||
@@ -123,7 +123,11 @@ export const NotebookSheet = () => {
|
||||
nestedNotebooks: notebooks,
|
||||
nestedNotebookNotesCount: totalNotes,
|
||||
groupOptions
|
||||
} = useNotebook(currentRoute === "Notebook" ? root : undefined);
|
||||
} = useNotebook(
|
||||
currentRoute === "Notebook" ? root : undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
const PLACEHOLDER_DATA = {
|
||||
heading: "Notebooks",
|
||||
@@ -140,17 +144,16 @@ export const NotebookSheet = () => {
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: string | GroupHeader;
|
||||
item: string | number;
|
||||
index: number;
|
||||
}) =>
|
||||
(item as GroupHeader).type === "header" ? null : (
|
||||
<NotebookItem
|
||||
items={notebooks}
|
||||
id={item as string}
|
||||
index={index}
|
||||
totalNotes={totalNotes}
|
||||
/>
|
||||
);
|
||||
}) => (
|
||||
<NotebookItem
|
||||
items={notebooks}
|
||||
id={item as string}
|
||||
index={index}
|
||||
totalNotes={totalNotes}
|
||||
/>
|
||||
);
|
||||
|
||||
const selectionContext = {
|
||||
selection: selection,
|
||||
@@ -396,7 +399,6 @@ export const NotebookSheet = () => {
|
||||
progressBackgroundColor={colors.primary.background}
|
||||
/>
|
||||
}
|
||||
keyExtractor={(item) => item as string}
|
||||
renderItem={renderNotebook}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
@@ -426,7 +428,7 @@ const NotebookItem = ({
|
||||
parent,
|
||||
items
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
totalNotes: (id: string) => number;
|
||||
currentLevel?: number;
|
||||
index: number;
|
||||
@@ -437,7 +439,7 @@ const NotebookItem = ({
|
||||
nestedNotebookNotesCount,
|
||||
nestedNotebooks,
|
||||
notebook: item
|
||||
} = useNotebook(id, items);
|
||||
} = useNotebook(id, items, true);
|
||||
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const selection = useSelection();
|
||||
@@ -445,7 +447,9 @@ const NotebookItem = ({
|
||||
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
|
||||
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||
const expanded = useNotebookExpandedStore((state) =>
|
||||
item?.id ? state.expanded[item?.id] : undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -511,7 +515,8 @@ const NotebookItem = ({
|
||||
size={SIZE.lg}
|
||||
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||
onPress={() => {
|
||||
useNotebookExpandedStore.getState().setExpanded(id);
|
||||
if (!item?.id) return;
|
||||
useNotebookExpandedStore.getState().setExpanded(item?.id);
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
@@ -553,9 +558,9 @@ const NotebookItem = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{totalNotes?.(id) ? (
|
||||
{item?.id && totalNotes?.(item?.id) ? (
|
||||
<Paragraph size={SIZE.sm} color={colors.secondary.paragraph}>
|
||||
{totalNotes(id)}
|
||||
{totalNotes(item?.id)}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
<IconButton
|
||||
@@ -580,10 +585,11 @@ const NotebookItem = ({
|
||||
|
||||
{!expanded
|
||||
? null
|
||||
: nestedNotebooks?.ids.map((id, index) => (
|
||||
: item &&
|
||||
nestedNotebooks?.ids.map((id, index) => (
|
||||
<NotebookItem
|
||||
key={id as string}
|
||||
id={id as string}
|
||||
key={item.id + "_" + id}
|
||||
id={index}
|
||||
index={index}
|
||||
totalNotes={nestedNotebookNotesCount}
|
||||
currentLevel={currentLevel + 1}
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { Attachment } from "@notesnook/core";
|
||||
|
||||
type AttachmentProgress = {
|
||||
type: string;
|
||||
@@ -27,7 +28,7 @@ type AttachmentProgress = {
|
||||
};
|
||||
|
||||
export const useAttachmentProgress = (
|
||||
attachment: any,
|
||||
attachment?: Attachment,
|
||||
encryption?: boolean
|
||||
): [
|
||||
AttachmentProgress | undefined,
|
||||
@@ -45,7 +46,9 @@ export const useAttachmentProgress = (
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const attachmentProgress = progress?.[attachment?.metadata?.hash];
|
||||
const attachmentProgress = !attachment
|
||||
? null
|
||||
: progress?.[attachment?.hash];
|
||||
if (attachmentProgress) {
|
||||
const type = attachmentProgress.type;
|
||||
const loaded =
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
Tag,
|
||||
VirtualizedGrouping
|
||||
} from "@notesnook/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -45,22 +45,36 @@ type ItemTypeKey = {
|
||||
shortcut: Shortcut;
|
||||
};
|
||||
|
||||
function isValidIdOrIndex(idOrIndex?: string | number) {
|
||||
return typeof idOrIndex === "number" || typeof idOrIndex === "string";
|
||||
}
|
||||
|
||||
export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
id?: string,
|
||||
idOrIndex?: string | number,
|
||||
type?: T,
|
||||
items?: VirtualizedGrouping<ItemTypeKey[T]>
|
||||
): [ItemTypeKey[T] | undefined, () => void] => {
|
||||
const [item, setItem] = useState<ItemTypeKey[T]>();
|
||||
const itemIdRef = useRef<string>();
|
||||
const prevIdOrIndexRef = useRef<string | number>();
|
||||
|
||||
if (prevIdOrIndexRef.current !== idOrIndex) {
|
||||
itemIdRef.current = undefined;
|
||||
prevIdOrIndexRef.current = idOrIndex;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onUpdateItem = (itemId?: string) => {
|
||||
if (typeof itemId === "string" && itemId !== id) return;
|
||||
if (!id) return;
|
||||
console.log("useDBItem.onUpdateItem", id, type);
|
||||
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
|
||||
|
||||
if (items) {
|
||||
items.item(id).then((item) => {
|
||||
setItem(item);
|
||||
if (!isValidIdOrIndex(idOrIndex)) return;
|
||||
|
||||
console.log("useDBItem.onUpdateItem", idOrIndex, type);
|
||||
|
||||
if (items && typeof idOrIndex === "number") {
|
||||
items.item(idOrIndex).then((item) => {
|
||||
setItem(item.item);
|
||||
itemIdRef.current = item.item.id;
|
||||
});
|
||||
} else {
|
||||
if (!(db as any)[type + "s"][type]) {
|
||||
@@ -69,9 +83,14 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
`db.${type}s.${type}(id: string)`
|
||||
);
|
||||
} else {
|
||||
console.log("get notebook");
|
||||
|
||||
(db as any)[type + "s"]
|
||||
?.[type]?.(id)
|
||||
.then((item: ItemTypeKey[T]) => setItem(item));
|
||||
?.[type]?.(idOrIndex as string)
|
||||
.then((item: ItemTypeKey[T]) => {
|
||||
setItem(item);
|
||||
itemIdRef.current = item.id;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -80,46 +99,42 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
return () => {
|
||||
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||
};
|
||||
}, [id, type, items]);
|
||||
}, [idOrIndex, type, items]);
|
||||
|
||||
return [
|
||||
id ? (item as ItemTypeKey[T]) : undefined,
|
||||
isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined,
|
||||
() => {
|
||||
if (id) {
|
||||
eSendEvent(eDBItemUpdate, id);
|
||||
if (idOrIndex) {
|
||||
eSendEvent(eDBItemUpdate, itemIdRef.current || idOrIndex);
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export const useTotalNotes = (
|
||||
ids: string[],
|
||||
type: "notebook" | "tag" | "color"
|
||||
) => {
|
||||
export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
|
||||
const [totalNotesById, setTotalNotesById] = useState<{
|
||||
[id: string]: number;
|
||||
}>({});
|
||||
|
||||
const getTotalNotes = React.useCallback(() => {
|
||||
if (!ids || !ids.length || !type) return;
|
||||
db.relations
|
||||
.from({ type: "notebook", ids: ids as string[] }, ["notebook", "note"])
|
||||
.get()
|
||||
.then((relations) => {
|
||||
const totalNotesById: any = {};
|
||||
for (const id of ids) {
|
||||
totalNotesById[id] = relations.filter(
|
||||
(relation) => relation.fromId === id && relation.toType === "note"
|
||||
)?.length;
|
||||
}
|
||||
setTotalNotesById(totalNotesById);
|
||||
});
|
||||
console.log("useTotalNotes.getTotalNotes");
|
||||
}, [ids, type]);
|
||||
|
||||
useEffect(() => {
|
||||
getTotalNotes();
|
||||
}, [ids, type, getTotalNotes]);
|
||||
const getTotalNotes = React.useCallback(
|
||||
(ids: string[]) => {
|
||||
if (!ids || !ids.length || !type) return;
|
||||
db.relations
|
||||
.from({ type: type, ids: ids as string[] }, ["note"])
|
||||
.get()
|
||||
.then((relations) => {
|
||||
const totalNotesById: any = {};
|
||||
for (const id of ids) {
|
||||
totalNotesById[id] = relations.filter(
|
||||
(relation) => relation.fromId === id && relation.toType === "note"
|
||||
)?.length;
|
||||
}
|
||||
setTotalNotesById(totalNotesById);
|
||||
});
|
||||
console.log("useTotalNotes.getTotalNotes");
|
||||
},
|
||||
[type]
|
||||
);
|
||||
|
||||
return {
|
||||
totalNotes: (id: string) => {
|
||||
|
||||
@@ -24,40 +24,47 @@ import { eGroupOptionsUpdated, eOnNotebookUpdated } from "../utils/events";
|
||||
import { useDBItem, useTotalNotes } from "./use-db-item";
|
||||
|
||||
export const useNotebook = (
|
||||
id?: string,
|
||||
items?: VirtualizedGrouping<Notebook>
|
||||
id?: string | number,
|
||||
items?: VirtualizedGrouping<Notebook>,
|
||||
nestedNotebooks?: boolean
|
||||
) => {
|
||||
const [item, refresh] = useDBItem(id, "notebook", items);
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings.getGroupOptions("notebooks")
|
||||
);
|
||||
const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>();
|
||||
const { totalNotes: nestedNotebookNotesCount } = useTotalNotes(
|
||||
notebooks?.ids as string[],
|
||||
"notebook"
|
||||
);
|
||||
const { totalNotes: nestedNotebookNotesCount, getTotalNotes } =
|
||||
useTotalNotes("notebook");
|
||||
|
||||
const onRequestUpdate = React.useCallback(() => {
|
||||
if (!id) return;
|
||||
console.log("useNotebook.onRequestUpdate", id, Date.now());
|
||||
db.relations
|
||||
.from(
|
||||
{
|
||||
type: "notebook",
|
||||
id: id
|
||||
},
|
||||
"notebook"
|
||||
)
|
||||
.selector.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
if (!item?.id) return;
|
||||
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
|
||||
|
||||
const selector = db.relations.from(
|
||||
{
|
||||
type: "notebook",
|
||||
id: item.id
|
||||
},
|
||||
"notebook"
|
||||
).selector;
|
||||
|
||||
selector.ids().then((notebookIds) => {
|
||||
getTotalNotes(notebookIds);
|
||||
});
|
||||
|
||||
selector
|
||||
.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
.then((notebooks) => {
|
||||
setNotebooks(notebooks);
|
||||
});
|
||||
}, [id]);
|
||||
}, [getTotalNotes, item?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("useNotebook.useEffect.onRequestUpdate");
|
||||
onRequestUpdate();
|
||||
}, [onRequestUpdate]);
|
||||
if (nestedNotebooks) {
|
||||
console.log("useNotebook.useEffect.onRequestUpdate");
|
||||
onRequestUpdate();
|
||||
}
|
||||
}, [item?.id, onRequestUpdate, nestedNotebooks]);
|
||||
|
||||
const onUpdate = useCallback(
|
||||
(type: string) => {
|
||||
@@ -73,7 +80,9 @@ export const useNotebook = (
|
||||
const onNotebookUpdate = (id?: string) => {
|
||||
if (typeof id === "string" && id !== id) return;
|
||||
setImmediate(() => {
|
||||
onRequestUpdate();
|
||||
if (nestedNotebooks) {
|
||||
onRequestUpdate();
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
};
|
||||
@@ -84,7 +93,7 @@ export const useNotebook = (
|
||||
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
|
||||
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
||||
};
|
||||
}, [onUpdate, onRequestUpdate, id, refresh]);
|
||||
}, [onUpdate, onRequestUpdate, id, refresh, nestedNotebooks]);
|
||||
|
||||
return {
|
||||
notebook: item,
|
||||
|
||||
@@ -33,11 +33,13 @@ export const useNoteStore = create<NoteStore>((set) => ({
|
||||
notes: undefined,
|
||||
loading: true,
|
||||
setLoading: (loading) => set({ loading: loading }),
|
||||
setNotes: () => {
|
||||
db.notes.all.grouped(db.settings.getGroupOptions("home")).then((notes) => {
|
||||
set({
|
||||
notes: notes
|
||||
});
|
||||
setNotes: async () => {
|
||||
const notes = await db.notes.all.grouped(
|
||||
db.settings.getGroupOptions("home")
|
||||
);
|
||||
await notes.item(0);
|
||||
set({
|
||||
notes: notes
|
||||
});
|
||||
},
|
||||
clearNotes: () => set({ notes: undefined })
|
||||
|
||||
@@ -17,20 +17,24 @@ 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, Note, Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||
import {
|
||||
Item,
|
||||
Note,
|
||||
Notebook,
|
||||
Tag,
|
||||
VirtualizedGrouping
|
||||
} from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
Platform,
|
||||
StatusBar,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import create from "zustand";
|
||||
import { db } from "../app/common/database";
|
||||
@@ -121,23 +125,29 @@ const NotebookItem = ({
|
||||
items,
|
||||
level = 0
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
mode: SearchMode;
|
||||
close?: () => void;
|
||||
level?: number;
|
||||
items?: VirtualizedGrouping<Notebook>;
|
||||
}) => {
|
||||
const isExpanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||
const { nestedNotebooks, notebook } = useNotebook(id, items);
|
||||
const isExpanded = useNotebookExpandedStore((state) =>
|
||||
!notebook ? false : state.expanded[notebook.id]
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const isSelected = useShareStore(
|
||||
(state) =>
|
||||
state.selectedNotebooks.findIndex((selectedId) => id === selectedId) > -1
|
||||
const isSelected = useShareStore((state) =>
|
||||
!notebook
|
||||
? false
|
||||
: state.selectedNotebooks.findIndex(
|
||||
(selectedId) => notebook?.id === selectedId
|
||||
) > -1
|
||||
);
|
||||
const set = SearchSetters[mode];
|
||||
|
||||
const onSelectItem = async () => {
|
||||
set(id);
|
||||
if (!notebook) return;
|
||||
set(notebook.id);
|
||||
};
|
||||
|
||||
return !notebook ? (
|
||||
@@ -172,7 +182,8 @@ const NotebookItem = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
onPress={() => {
|
||||
useNotebookExpandedStore.getState().setExpanded(id);
|
||||
if (!notebook) return;
|
||||
useNotebookExpandedStore.getState().setExpanded(notebook.id);
|
||||
}}
|
||||
activeOpacity={1}
|
||||
>
|
||||
@@ -230,10 +241,10 @@ const NotebookItem = ({
|
||||
marginTop: 5
|
||||
}}
|
||||
>
|
||||
{(nestedNotebooks.ids as string[]).map((id) => (
|
||||
{nestedNotebooks.ids.map((item, index) => (
|
||||
<NotebookItem
|
||||
key={id}
|
||||
id={id}
|
||||
key={notebook?.id + index}
|
||||
id={index}
|
||||
mode={mode}
|
||||
close={close}
|
||||
level={level + 1}
|
||||
@@ -251,7 +262,7 @@ const ListItem = ({
|
||||
close,
|
||||
items
|
||||
}: {
|
||||
id: string;
|
||||
id: string | number;
|
||||
mode: SearchMode;
|
||||
close?: () => void;
|
||||
items?: VirtualizedGrouping<Item>;
|
||||
@@ -259,21 +270,23 @@ const ListItem = ({
|
||||
const [item] = useDBItem(
|
||||
id,
|
||||
mode === "appendNote" ? "note" : "tag",
|
||||
items as any
|
||||
items as VirtualizedGrouping<Note | Tag>
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const isSelected = useShareStore((state) =>
|
||||
mode === "appendNote" ? false : state.selectedTags.indexOf(id as never) > -1
|
||||
mode === "appendNote" || !item
|
||||
? false
|
||||
: state.selectedTags.indexOf(item?.id as never) > -1
|
||||
);
|
||||
|
||||
const set = SearchSetters[mode];
|
||||
|
||||
const onSelectItem = async () => {
|
||||
if ((item as Note)?.locked) {
|
||||
if ((item as Note)?.locked || !item) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(id);
|
||||
set(item.id);
|
||||
};
|
||||
|
||||
return !item ? null : (
|
||||
@@ -356,12 +369,6 @@ export const Search = ({
|
||||
.then((exists) => setQueryExists(!!exists));
|
||||
};
|
||||
|
||||
const insets =
|
||||
Platform.OS === "android"
|
||||
? { top: StatusBar.currentHeight }
|
||||
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useSafeAreaInsets();
|
||||
|
||||
const get = SearchGetters[mode];
|
||||
const lookup = SearchLookup[mode];
|
||||
|
||||
@@ -389,16 +396,16 @@ export const Search = ({
|
||||
}, [get]);
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ item }: { item: string }) =>
|
||||
({ index }: { item: string | number; index: number }) =>
|
||||
mode === "selectNotebooks" ? (
|
||||
<NotebookItem
|
||||
id={item}
|
||||
id={index}
|
||||
mode={mode}
|
||||
close={close}
|
||||
items={items as any}
|
||||
items={items as VirtualizedGrouping<Notebook>}
|
||||
/>
|
||||
) : (
|
||||
<ListItem id={item} mode={mode} close={close} items={items} />
|
||||
<ListItem id={index} mode={mode} close={close} items={items} />
|
||||
),
|
||||
[close, mode, items]
|
||||
);
|
||||
@@ -502,7 +509,7 @@ export const Search = ({
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
data={items?.ids as string[]}
|
||||
data={items?.ids}
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="none"
|
||||
renderItem={renderItem}
|
||||
|
||||
Reference in New Issue
Block a user