From 04d66576cb0651fc2fa6ddbd229f61a2571ef84c Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Wed, 24 Jan 2024 16:22:28 +0500 Subject: [PATCH] web: show references & linked notes in properties --- apps/web/src/components/properties/index.tsx | 327 ++++++++++++++++++- apps/web/src/dialogs/note-linking-dialog.tsx | 2 +- 2 files changed, 324 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/properties/index.tsx b/apps/web/src/components/properties/index.tsx index 9c343bffd..5c0cb2659 100644 --- a/apps/web/src/components/properties/index.tsx +++ b/apps/web/src/components/properties/index.tsx @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import React, { PropsWithChildren } from "react"; +import React, { PropsWithChildren, useRef, useState } from "react"; import { Pin, StarOutline, @@ -26,9 +26,11 @@ import { SyncOff, ArrowLeft, Circle, - Checkmark + Checkmark, + ChevronDown, + ChevronRight } from "../icons"; -import { Flex, Text } from "@theme-ui/components"; +import { Box, Button, Flex, Text } from "@theme-ui/components"; import { useEditorStore, ReadonlyEditorSession, @@ -40,12 +42,24 @@ import { store as noteStore } from "../../stores/note-store"; import { AnimatedFlex } from "../animated"; import Toggle from "./toggle"; import ScrollContainer from "../scroll-container"; -import { ResolvedItem, getFormattedDate, usePromise } from "@notesnook/common"; +import { + getFormattedDate, + usePromise, + ResolvedItem, + useResolvedItem +} from "@notesnook/common"; import { ScopedThemeProvider } from "../theme-provider"; import { ListItemWrapper } from "../list-container/list-profiles"; import { VirtualizedList } from "../virtualized-list"; import { SessionItem } from "../session-item"; +import { + ContentBlock, + Note, + VirtualizedGrouping, + highlightInternalLinks +} from "@notesnook/core"; import { VirtualizedTable } from "../virtualized-table"; +import { TextSlice } from "@notesnook/core/dist/utils/content-block"; const tools = [ { key: "pin", property: "pinned", icon: Pin, label: "Pin" }, @@ -117,6 +131,7 @@ function EditorProperties(props: EditorPropertiesProps) { sx={{ display: "flex", position: "absolute", + top: 0, right: 0, zIndex: 1, height: "100%", @@ -185,6 +200,7 @@ function EditorProperties(props: EditorPropertiesProps) { ))} + @@ -196,6 +212,308 @@ function EditorProperties(props: EditorPropertiesProps) { } export default React.memo(EditorProperties); +enum InternalLinksTabs { + LINKED_NOTES = 0, + REFERENCED_IN = 1 +} +function InternalLinks({ noteId }: { noteId: string }) { + const [tabIndex, setTabIndex] = useState(InternalLinksTabs.LINKED_NOTES); + const [expandedId, setExpandedId] = useState(); + + const result = usePromise(() => { + const links = + tabIndex === InternalLinksTabs.LINKED_NOTES + ? db.relations.from({ id: noteId, type: "note" }, "note") + : db.relations.to({ id: noteId, type: "note" }, "note"); + return links.selector.sorted(db.settings.getGroupOptions("notes")); + }, [tabIndex]); + + return ( + + + {["Linked notes", "Referenced in"].map((title, index) => ( + + ))} + + + {result.status === "fulfilled" && ( + result.value.key(index)} + items={result.value.placeholders} + context={{ + items: result.value, + tabIndex, + noteId, + isExpanded: (id) => expandedId === id, + toggleExpand: (id) => + setExpandedId((s) => (s === id ? undefined : id)) + }} + renderItem={InternalLinkItem} + /> + )} + + ); +} + +function InternalLinkItem({ + index, + context +}: { + item: boolean; + index: number; + context: { + items: VirtualizedGrouping; + tabIndex: InternalLinksTabs; + noteId: string; + isExpanded: (id: string) => boolean; + toggleExpand: (id: string) => void; + }; +}) { + const { items, tabIndex, noteId, isExpanded, toggleExpand } = context; + const item = useResolvedItem({ items, index }); + + if (!item || item.item.type !== "note") return null; + + if (tabIndex === InternalLinksTabs.LINKED_NOTES) + return ( + toggleExpand(item.item.id)} + /> + ); + return ( + toggleExpand(item.item.id)} + /> + ); +} +function LinkedNote({ + item, + noteId, + isExpanded, + toggleExpand +}: { + item: Note; + noteId: string; + toggleExpand: () => void; + isExpanded: boolean; +}) { + const [blocks, setBlocks] = useState([]); + + return ( + <> + + {isExpanded + ? blocks.map((block) => ( + + )) + : null} + + ); +} + +function ReferencedIn({ + item, + noteId, + isExpanded, + toggleExpand +}: { + item: Note; + noteId: string; + toggleExpand: () => void; + isExpanded: boolean; +}) { + const [blocks, setBlocks] = useState< + { id: string; links: [TextSlice, TextSlice, TextSlice][] }[] + >([]); + + return ( + <> + + {isExpanded + ? blocks.map((block) => ( + <> + {block.links.map((link, index) => ( + + ))} + + )) + : null} + + ); +} + function Colors({ noteId, color }: { noteId: string; color?: string }) { const result = usePromise(() => db.colors.all.items(), [color]); return ( @@ -268,6 +586,7 @@ function Notebooks({ noteId }: { noteId: string }) { ); } + function Reminders({ noteId }: { noteId: string }) { const result = usePromise(() => db.relations diff --git a/apps/web/src/dialogs/note-linking-dialog.tsx b/apps/web/src/dialogs/note-linking-dialog.tsx index bae4c69bd..f22a4d46e 100644 --- a/apps/web/src/dialogs/note-linking-dialog.tsx +++ b/apps/web/src/dialogs/note-linking-dialog.tsx @@ -162,7 +162,7 @@ export default function NoteLinkingDialog(props: NoteLinkingDialogProps) { sx={{ p: 1, width: "100%", textAlign: "left" }} onClick={async () => { setSelectedNote(note); - setBlocks(await db.notes.getBlocks(note.id)); + setBlocks(await db.notes.contentBlocks(note.id)); }} > {note.title}