/*
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 .
*/
import {
InternalLink,
TextSlice,
VirtualizedGrouping,
createInternalLink,
highlightInternalLinks
} from "@notesnook/core";
import { ContentBlock, ItemReference, Note } from "@notesnook/core/dist/types";
import { useThemeColors } from "@notesnook/theme";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import create from "zustand";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnLoadNote } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { SIZE } from "../../../utils/size";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
export const useExpandedStore = create<{
expanded: {
[id: string]: boolean;
};
setExpanded: (id: string) => void;
}>((set, get) => ({
expanded: {},
setExpanded(id: string) {
set({
expanded: {
...get().expanded,
[id]: !get().expanded[id]
}
});
}
}));
const ListBlockItem = ({
item,
onSelectBlock
}: {
item: ContentBlock;
onSelectBlock: () => void;
}) => {
const { colors } = useThemeColors();
return (
{
onSelectBlock();
}}
type={"transparent"}
customStyle={{
flexDirection: "row",
width: "100%",
paddingLeft: 35,
justifyContent: "flex-start",
minHeight: 45,
paddingHorizontal: 12
}}
>
{item.type.toUpperCase()}
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: item.content}
);
};
const ListNoteInternalLink = ({
link,
onSelect
}: {
link: {
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
};
onSelect: () => void;
}) => {
const { colors } = useThemeColors();
return (
{
onSelect();
}}
type={"transparent"}
customStyle={{
flexDirection: "row",
width: "100%",
paddingLeft: 35,
justifyContent: "flex-start",
minHeight: 45
}}
>
{link.highlightedText.map((text) => (
{text.map((slice) =>
!slice.highlighted ? (
slice.text
) : (
{slice.text}
)
)}
))}
);
};
const ListNoteItem = ({
id,
items,
onSelect,
reference,
internalLinks,
listType
}: {
id: number;
items: VirtualizedGrouping | undefined;
onSelect: (item: Note, blockId?: string) => void;
reference: Note;
internalLinks: MutableRefObject[] | undefined>;
listType: "linkedNotes" | "referencedIn";
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(id, "note", items);
const expanded = useExpandedStore((state) =>
!item ? false : state.expanded[item.id]
);
const [linkedBlocks, setLinkedBlocks] = useState([]);
const [noteInternalLinks, setNoteInternalLinks] = useState<
{
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
}[]
>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (item?.id && expanded) {
(async () => {
if (listType === "linkedNotes") {
if (linkedBlocks.length) return;
setLoading(true);
if (!internalLinks.current) {
internalLinks.current = await db.notes.internalLinks(reference.id);
}
const noteLinks = internalLinks.current.filter(
(link) => link.id === item.id && link.params?.blockId
);
if (noteLinks.length) {
const blocks = await db.notes.contentBlocks(item.id);
setLinkedBlocks(
blocks.filter((block) =>
noteLinks.find((link) => block.id === link.params?.blockId)
)
);
}
} else {
if (noteInternalLinks.length) return;
setLoading(true);
const blocks = await db.notes.contentBlocks(item.id);
setNoteInternalLinks(
blocks
.filter((block) =>
block.content.includes(createInternalLink("note", reference.id))
)
.map((block) => {
return {
blockId: block?.id as string,
highlightedText: highlightInternalLinks(
block as ContentBlock,
reference.id
)
};
})
);
}
setLoading(false);
})();
}
}, [
item?.id,
expanded,
linkedBlocks.length,
internalLinks,
reference.id,
listType,
noteInternalLinks.length
]);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
{
if (!item) return;
onSelect(item, block.id);
}}
/>
),
[item, onSelect]
);
const renderInternalLink = React.useCallback(
(link: {
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
}) => (
{
if (!item) return;
onSelect(item, link.blockId);
}}
/>
),
[item, onSelect]
);
return (
{
if (!item) return;
onSelect(item as Note);
}}
customStyle={{
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
height: 45
}}
>
{
if (!item?.id) return;
useExpandedStore.getState().setExpanded(item?.id);
}}
top={0}
left={0}
bottom={0}
right={0}
customStyle={{
width: 35,
height: 35
}}
name={expanded ? "chevron-down" : "chevron-right"}
/>
{item?.title}
{expanded && !item?.locked ? (
{loading ? (
) : (
<>
{listType === "linkedNotes" ? (
<>
{linkedBlocks.length === 0 ? (
No blocks linked
) : (
linkedBlocks.map(renderBlock)
)}
>
) : (
<>
{noteInternalLinks.length === 0 ? (
No references found of this note
) : (
noteInternalLinks.map(renderInternalLink)
)}
>
)}
>
)}
) : null}
);
};
type ReferencesListProps = {
item: { id: string; type: string };
close?: (ctx?: any) => void;
};
export const ReferencesList = ({ item, close }: ReferencesListProps) => {
const [tab, setTab] = useState(0);
const updater = useRelationStore((state) => state.updater);
const { colors } = useThemeColors();
const [items, setItems] = useState>();
const hasNoRelations = !items || items?.placeholders?.length === 0;
const internalLinks = useRef[]>();
useEffect(() => {
db.relations?.[tab === 0 ? "from" : "to"]?.(
{ id: item?.id, type: item?.type } as ItemReference,
"note"
)
.selector.sorted({
sortBy: "dateEdited",
sortDirection: "desc"
})
.then((items) => {
setItems(items);
});
}, [item?.id, item?.type, tab]);
const renderNote = React.useCallback(
({ index }: any) => (
{
console.log(note.id, blockId);
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId
});
tabBarRef.current?.goToPage(1);
close?.();
}}
reference={item as Note}
internalLinks={internalLinks}
listType={tab === 0 ? "linkedNotes" : "referencedIn"}
/>
),
[items, item, tab, close]
);
return (
{hasNoRelations ? (
{tab === 1
? "This note is not referenced in other notes."
: "This note does not link to other notes."}
) : (
)}
);
};
ReferencesList.present = ({
reference
}: {
reference: { id: string; type: string };
}) => {
presentSheet({
component: (ref, close, update) => (
),
onClose: () => {
useExpandedStore.setState({
expanded: {}
});
}
});
};