From 5cf18ae17c475fe2b6601170f7349f6901b4fcf3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 30 Aug 2025 17:21:22 +0300 Subject: [PATCH] chore(react/collections/view): first implementation --- .../src/widgets/collections/NoteList.tsx | 14 ++- .../src/widgets/collections/Pagination.tsx | 1 - .../collections/legacy/ListOrGridView.css | 1 - .../widgets/collections/legacy/ListView.tsx | 105 ++++++++++++++---- .../widgets/view_widgets/list_or_grid_view.ts | 36 +----- 5 files changed, 91 insertions(+), 66 deletions(-) diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index d57c66871..d749e2844 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -2,7 +2,7 @@ import { allViewTypes, ViewModeProps, ViewTypeOptions } from "./interface"; import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; import FNote from "../../entities/fnote"; import "./NoteList.css"; -import ListView from "./legacy/ListView"; +import { ListView, GridView } from "./legacy/ListView"; import { useEffect, useState } from "preact/hooks"; interface NoteListProps { @@ -30,16 +30,18 @@ export default function NoteList({ }: NoteListProps) { function getComponentByViewType(note: FNote, noteIds: string[], viewType: ViewTypeOptions) { const props: ViewModeProps = { note, noteIds }; - + switch (viewType) { case "list": return ; + case "grid": + return ; } } function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { const [ viewType ] = useNoteLabel(note, "viewType"); - + if (!note) { return undefined; } else if (!(allViewTypes as readonly string[]).includes(viewType || "")) { @@ -52,7 +54,7 @@ function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) { const [ noteIds, setNoteIds ] = useState([]); - + async function refreshNoteIds() { if (!note) { setNoteIds([]); @@ -73,9 +75,9 @@ function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | if (note && loadResults.getBranchRows().some(branch => branch.parentNoteId === note.noteId || noteIds.includes(branch.parentNoteId ?? ""))) { - refreshNoteIds(); + refreshNoteIds(); } }) return noteIds; -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/collections/Pagination.tsx b/apps/client/src/widgets/collections/Pagination.tsx index 435f9049b..969a173cc 100644 --- a/apps/client/src/widgets/collections/Pagination.tsx +++ b/apps/client/src/widgets/collections/Pagination.tsx @@ -52,7 +52,6 @@ export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit

{children} - // no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all ({t("pagination.total_notes", { count: totalNotes })}) ) diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.css b/apps/client/src/widgets/collections/legacy/ListOrGridView.css index 21e60c981..f981144b4 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.css +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.css @@ -17,7 +17,6 @@ } .note-book-card:not(.expanded) .note-book-content { - display: none !important; padding: 10px } diff --git a/apps/client/src/widgets/collections/legacy/ListView.tsx b/apps/client/src/widgets/collections/legacy/ListView.tsx index bb61ef04c..46450707e 100644 --- a/apps/client/src/widgets/collections/legacy/ListView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListView.tsx @@ -7,45 +7,60 @@ import NoteLink from "../../react/NoteLink"; import "./ListOrGridView.css"; import content_renderer from "../../../services/content_renderer"; import { Pager, usePagination } from "../Pagination"; +import tree from "../../../services/tree"; +import link from "../../../services/link"; -export default function ListView({ note, noteIds }: ViewModeProps) { +export function ListView({ note, noteIds: unfilteredNoteIds }: ViewModeProps) { const [ isExpanded ] = useNoteLabelBoolean(note, "expanded"); - const filteredNoteIds = useMemo(() => { - // Filters the note IDs for the legacy view to filter out subnotes that are already included in the note content such as images, included notes. - const includedLinks = note ? note.getRelations().filter((rel) => rel.name === "imageLink" || rel.name === "includeNoteLink") : []; - const includedNoteIds = new Set(includedLinks.map((rel) => rel.value)); - return noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden"); - }, noteIds); - const { pageNotes, ...pagination } = usePagination(note, filteredNoteIds); + const noteIds = useFilteredNoteIds(note, unfilteredNoteIds); + const { pageNotes, ...pagination } = usePagination(note, noteIds); return ( -

+
- + - +
); } -function NoteCard({ note, expand }: { note: FNote, expand?: boolean }) { +export function GridView({ note, noteIds: unfilteredNoteIds }: ViewModeProps) { + const noteIds = useFilteredNoteIds(note, unfilteredNoteIds); + const { pageNotes, ...pagination } = usePagination(note, noteIds); + + return ( +
+
+ + + + + +
+
+ ); +} + +function ListNoteCard({ note, parentNote, expand }: { note: FNote, parentNote: FNote, expand?: boolean }) { const [ isExpanded, setExpanded ] = useState(expand); - const isSearch = note.type === "search"; - const notePath = isSearch - ? note.noteId // for search note parent, we want to display a non-search path - : `${note.noteId}/${note.noteId}`; + const notePath = getNotePath(parentNote, note); return (
- + {isExpanded && <> - + }
) } +function GridNoteCard({ note, parentNote }: { note: FNote, parentNote: FNote }) { + const [ noteTitle, setNoteTitle ] = useState(); + const notePath = getNotePath(parentNote, note); + + useEffect(() => { + tree.getNoteTitle(note.noteId, parentNote.noteId).then(setNoteTitle); + }, [ note ]); + + return ( +
link.goToLink(e)} + > +
+ + {noteTitle} +
+ +
+ ) +} + function NoteContent({ note, trim }: { note: FNote, trim?: boolean }) { const contentRef = useRef(null); @@ -83,7 +122,7 @@ function NoteContent({ note, trim }: { note: FNote, trim?: boolean }) { return
; } -function NoteChildren({ note }: { note: FNote}) { +function NoteChildren({ note, parentNote }: { note: FNote, parentNote: FNote }) { const imageLinks = note.getRelations("imageLink"); const [ childNotes, setChildNotes ] = useState(); @@ -94,5 +133,25 @@ function NoteChildren({ note }: { note: FNote}) { }); }, [ note ]); - return childNotes?.map(childNote => ) + return childNotes?.map(childNote => ) +} + +/** + * Filters the note IDs for the legacy view to filter out subnotes that are already included in the note content such as images, included notes. + */ +function useFilteredNoteIds(note: FNote, noteIds: string[]) { + return useMemo(() => { + const includedLinks = note ? note.getRelations().filter((rel) => rel.name === "imageLink" || rel.name === "includeNoteLink") : []; + const includedNoteIds = new Set(includedLinks.map((rel) => rel.value)); + return noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden"); + }, noteIds); +} + +function getNotePath(parentNote: FNote, childNote: FNote) { + if (parentNote.type === "search") { + // for search note parent, we want to display a non-search path + return childNote.noteId; + } else { + return `${parentNote.noteId}/${childNote.noteId}` + } } diff --git a/apps/client/src/widgets/view_widgets/list_or_grid_view.ts b/apps/client/src/widgets/view_widgets/list_or_grid_view.ts index 0b1d6b579..28f2cb866 100644 --- a/apps/client/src/widgets/view_widgets/list_or_grid_view.ts +++ b/apps/client/src/widgets/view_widgets/list_or_grid_view.ts @@ -13,7 +13,6 @@ class ListOrGridView extends ViewMode<{}> { private filteredNoteIds!: string[]; private page?: number; private pageSize?: number; - private showNotePath?: boolean; private highlightRegex?: RegExp | null; constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { @@ -45,25 +44,6 @@ class ListOrGridView extends ViewMode<{}> { async renderNote(note: FNote, expand: boolean = false) { const { $renderedAttributes } = await attributeRenderer.renderNormalAttributes(note); - const $card = $('
') - .append( - $('
') - .append( - this.viewType === "grid" - ? $('').text(await treeService.getNoteTitle(note.noteId, this.parentNote.noteId)) - ) - .append($renderedAttributes) - ); - - if (this.viewType === "grid") { - $card - .addClass("block-link") - .attr("data-href", `#${notePath}`) - .on("click", (e) => linkService.goToLink(e)); - } - - $expander.on("click", () => this.toggleContent($card, note, !$card.hasClass("expanded"))); - if (this.highlightRegex) { const Mark = new (await import("mark.js")).default($card.find(".note-book-title")[0]); Mark.markRegExp(this.highlightRegex, { @@ -71,26 +51,12 @@ class ListOrGridView extends ViewMode<{}> { className: "ck-find-result" }); } - - await this.toggleContent($card, note, expand); - - return $card; - } - - async toggleContent($card: JQuery, note: FNote, expand: boolean) { - if (this.viewType === "list" && ((expand && $card.hasClass("expanded")) || (!expand && !$card.hasClass("expanded")))) { - return; - } - - if ((this.viewType === "grid")) { - $card.append(await this.renderNoteContent(note)); - } } async renderNoteContent(note: FNote) { try { const { $renderedContent, type } = await contentRenderer.getRenderedContent(note, { - trim: this.viewType === "grid" // for grid only short content is needed + trim: this.viewType === "grid" }); if (this.highlightRegex) {