mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	chore(react/collections/view): first implementation
This commit is contained in:
		| @@ -2,7 +2,7 @@ import { allViewTypes, ViewModeProps, ViewTypeOptions } from "./interface"; | |||||||
| import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; | import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; | ||||||
| import FNote from "../../entities/fnote"; | import FNote from "../../entities/fnote"; | ||||||
| import "./NoteList.css"; | import "./NoteList.css"; | ||||||
| import ListView from "./legacy/ListView"; | import { ListView, GridView } from "./legacy/ListView"; | ||||||
| import { useEffect, useState } from "preact/hooks"; | import { useEffect, useState } from "preact/hooks"; | ||||||
|  |  | ||||||
| interface NoteListProps { | interface NoteListProps { | ||||||
| @@ -30,16 +30,18 @@ export default function NoteList({ }: NoteListProps) { | |||||||
|  |  | ||||||
| function getComponentByViewType(note: FNote, noteIds: string[], viewType: ViewTypeOptions) { | function getComponentByViewType(note: FNote, noteIds: string[], viewType: ViewTypeOptions) { | ||||||
|     const props: ViewModeProps = { note, noteIds }; |     const props: ViewModeProps = { note, noteIds }; | ||||||
|      |  | ||||||
|     switch (viewType) { |     switch (viewType) { | ||||||
|         case "list": |         case "list": | ||||||
|             return <ListView {...props} />; |             return <ListView {...props} />; | ||||||
|  |         case "grid": | ||||||
|  |             return <GridView {...props} />; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { | function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { | ||||||
|     const [ viewType ] = useNoteLabel(note, "viewType"); |     const [ viewType ] = useNoteLabel(note, "viewType"); | ||||||
|      |  | ||||||
|     if (!note) { |     if (!note) { | ||||||
|         return undefined; |         return undefined; | ||||||
|     } else if (!(allViewTypes as readonly string[]).includes(viewType || "")) { |     } 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) { | function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) { | ||||||
|     const [ noteIds, setNoteIds ] = useState<string[]>([]); |     const [ noteIds, setNoteIds ] = useState<string[]>([]); | ||||||
|      |  | ||||||
|     async function refreshNoteIds() { |     async function refreshNoteIds() { | ||||||
|         if (!note) { |         if (!note) { | ||||||
|             setNoteIds([]); |             setNoteIds([]); | ||||||
| @@ -73,9 +75,9 @@ function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | | |||||||
|         if (note && loadResults.getBranchRows().some(branch => |         if (note && loadResults.getBranchRows().some(branch => | ||||||
|                 branch.parentNoteId === note.noteId |                 branch.parentNoteId === note.noteId | ||||||
|                 || noteIds.includes(branch.parentNoteId ?? ""))) { |                 || noteIds.includes(branch.parentNoteId ?? ""))) { | ||||||
|             refreshNoteIds();     |             refreshNoteIds(); | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     return noteIds; |     return noteIds; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -52,7 +52,6 @@ export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<P | |||||||
|         <div class="note-list-pager"> |         <div class="note-list-pager"> | ||||||
|             {children} |             {children} | ||||||
|  |  | ||||||
|             // no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all |  | ||||||
|             <span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span> |             <span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span> | ||||||
|         </div> |         </div> | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| .note-book-card:not(.expanded) .note-book-content { | .note-book-card:not(.expanded) .note-book-content { | ||||||
|     display: none !important; |  | ||||||
|     padding: 10px |     padding: 10px | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,45 +7,60 @@ import NoteLink from "../../react/NoteLink"; | |||||||
| import "./ListOrGridView.css"; | import "./ListOrGridView.css"; | ||||||
| import content_renderer from "../../../services/content_renderer"; | import content_renderer from "../../../services/content_renderer"; | ||||||
| import { Pager, usePagination } from "../Pagination"; | 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 [ isExpanded ] = useNoteLabelBoolean(note, "expanded"); | ||||||
|     const filteredNoteIds = useMemo(() => { |     const noteIds = useFilteredNoteIds(note, unfilteredNoteIds); | ||||||
|         // 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 { pageNotes, ...pagination } = usePagination(note, noteIds); | ||||||
|         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); |  | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <div class="note-list"> |         <div class="note-list list-view"> | ||||||
|             <div class="note-list-wrapper"> |             <div class="note-list-wrapper"> | ||||||
|                 <Pager {...pagination} /> |                 <Pager {...pagination} /> | ||||||
|          |  | ||||||
|                 <div class="note-list-container use-tn-links"> |                 <div class="note-list-container use-tn-links"> | ||||||
|                     {pageNotes?.map(note => ( |                     {pageNotes?.map(childNote => ( | ||||||
|                         <NoteCard note={note} expand={isExpanded} /> |                         <ListNoteCard note={childNote} parentNote={note} expand={isExpanded} /> | ||||||
|                     ))} |                     ))} | ||||||
|                 </div> |                 </div> | ||||||
|          |  | ||||||
|                 <Pager {...pagination} /> |                 <Pager {...pagination} /> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 ( | ||||||
|  |         <div class="note-list grid-view"> | ||||||
|  |             <div class="note-list-wrapper"> | ||||||
|  |                 <Pager {...pagination} /> | ||||||
|  |  | ||||||
|  |                 <div class="note-list-container use-tn-links"> | ||||||
|  |                     {pageNotes?.map(childNote => ( | ||||||
|  |                         <GridNoteCard note={childNote} parentNote={note} /> | ||||||
|  |                     ))} | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <Pager {...pagination} /> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function ListNoteCard({ note, parentNote, expand }: { note: FNote, parentNote: FNote, expand?: boolean }) { | ||||||
|     const [ isExpanded, setExpanded ] = useState(expand); |     const [ isExpanded, setExpanded ] = useState(expand); | ||||||
|     const isSearch = note.type === "search"; |     const notePath = getNotePath(parentNote, note); | ||||||
|     const notePath = isSearch |  | ||||||
|         ? note.noteId // for search note parent, we want to display a non-search path |  | ||||||
|         : `${note.noteId}/${note.noteId}`; |  | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <div |         <div | ||||||
|             className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""}`} |             className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""}`} | ||||||
|             data-note-id={note.noteId}             |             data-note-id={note.noteId} | ||||||
|         > |         > | ||||||
|             <h5 className="note-book-header"> |             <h5 className="note-book-header"> | ||||||
|                 <span |                 <span | ||||||
| @@ -54,16 +69,40 @@ function NoteCard({ note, expand }: { note: FNote, expand?: boolean }) { | |||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <Icon className="note-icon" icon={note.getIcon()} /> |                 <Icon className="note-icon" icon={note.getIcon()} /> | ||||||
|                 <NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={isSearch} /> |                 <NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={note.type === "search"} /> | ||||||
|                 {isExpanded && <> |                 {isExpanded && <> | ||||||
|                     <NoteContent note={note} /> |                     <NoteContent note={note} /> | ||||||
|                     <NoteChildren note={note} /> |                     <NoteChildren note={note} parentNote={parentNote} /> | ||||||
|                 </>} |                 </>} | ||||||
|             </h5> |             </h5> | ||||||
|         </div> |         </div> | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function GridNoteCard({ note, parentNote }: { note: FNote, parentNote: FNote }) { | ||||||
|  |     const [ noteTitle, setNoteTitle ] = useState<string>(); | ||||||
|  |     const notePath = getNotePath(parentNote, note); | ||||||
|  |  | ||||||
|  |     useEffect(() => { | ||||||
|  |         tree.getNoteTitle(note.noteId, parentNote.noteId).then(setNoteTitle); | ||||||
|  |     }, [ note ]); | ||||||
|  |  | ||||||
|  |     return ( | ||||||
|  |         <div | ||||||
|  |             className={`note-book-card no-tooltip-preview block-link`} | ||||||
|  |             data-href={`#${notePath}`} | ||||||
|  |             data-note-id={note.noteId} | ||||||
|  |             onClick={(e) => link.goToLink(e)} | ||||||
|  |         > | ||||||
|  |             <h5 className="note-book-header"> | ||||||
|  |                 <Icon className="note-icon" icon={note.getIcon()} /> | ||||||
|  |                 <span className="note-book-title">{noteTitle}</span> | ||||||
|  |             </h5> | ||||||
|  |             <NoteContent note={note} trim /> | ||||||
|  |         </div> | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
| function NoteContent({ note, trim }: { note: FNote, trim?: boolean }) { | function NoteContent({ note, trim }: { note: FNote, trim?: boolean }) { | ||||||
|     const contentRef = useRef<HTMLDivElement>(null); |     const contentRef = useRef<HTMLDivElement>(null); | ||||||
|  |  | ||||||
| @@ -83,7 +122,7 @@ function NoteContent({ note, trim }: { note: FNote, trim?: boolean }) { | |||||||
|     return <div ref={contentRef} className="note-book-content" />; |     return <div ref={contentRef} className="note-book-content" />; | ||||||
| } | } | ||||||
|  |  | ||||||
| function NoteChildren({ note }: { note: FNote}) { | function NoteChildren({ note, parentNote }: { note: FNote, parentNote: FNote }) { | ||||||
|     const imageLinks = note.getRelations("imageLink"); |     const imageLinks = note.getRelations("imageLink"); | ||||||
|     const [ childNotes, setChildNotes ] = useState<FNote[]>(); |     const [ childNotes, setChildNotes ] = useState<FNote[]>(); | ||||||
|  |  | ||||||
| @@ -94,5 +133,25 @@ function NoteChildren({ note }: { note: FNote}) { | |||||||
|         }); |         }); | ||||||
|     }, [ note ]); |     }, [ note ]); | ||||||
|  |  | ||||||
|     return childNotes?.map(childNote => <NoteCard note={childNote} />) |     return childNotes?.map(childNote => <ListNoteCard note={childNote} parentNote={parentNote} />) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 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}` | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|     private filteredNoteIds!: string[]; |     private filteredNoteIds!: string[]; | ||||||
|     private page?: number; |     private page?: number; | ||||||
|     private pageSize?: number; |     private pageSize?: number; | ||||||
|     private showNotePath?: boolean; |  | ||||||
|     private highlightRegex?: RegExp | null; |     private highlightRegex?: RegExp | null; | ||||||
|  |  | ||||||
|     constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { |     constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { | ||||||
| @@ -45,25 +44,6 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|     async renderNote(note: FNote, expand: boolean = false) { |     async renderNote(note: FNote, expand: boolean = false) { | ||||||
|         const { $renderedAttributes } = await attributeRenderer.renderNormalAttributes(note); |         const { $renderedAttributes } = await attributeRenderer.renderNormalAttributes(note); | ||||||
|  |  | ||||||
|         const $card = $('<div class="note-book-card">') |  | ||||||
|             .append( |  | ||||||
|                 $('<h5 class="note-book-header">') |  | ||||||
|                     .append( |  | ||||||
|                         this.viewType === "grid" |  | ||||||
|                             ? $('<span class="note-book-title">').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) { |         if (this.highlightRegex) { | ||||||
|             const Mark = new (await import("mark.js")).default($card.find(".note-book-title")[0]); |             const Mark = new (await import("mark.js")).default($card.find(".note-book-title")[0]); | ||||||
|             Mark.markRegExp(this.highlightRegex, { |             Mark.markRegExp(this.highlightRegex, { | ||||||
| @@ -71,26 +51,12 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|                 className: "ck-find-result" |                 className: "ck-find-result" | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await this.toggleContent($card, note, expand); |  | ||||||
|  |  | ||||||
|         return $card; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async toggleContent($card: JQuery<HTMLElement>, 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) { |     async renderNoteContent(note: FNote) { | ||||||
|         try { |         try { | ||||||
|             const { $renderedContent, type } = await contentRenderer.getRenderedContent(note, { |             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) { |             if (this.highlightRegex) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user