mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	chore(react/collections): get list view to show something
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| import { allViewTypes, ViewTypeOptions } from "./interface"; | 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"; | ||||||
| @@ -13,27 +13,27 @@ export default function NoteList({ }: NoteListProps) { | |||||||
|     const { note } = useNoteContext(); |     const { note } = useNoteContext(); | ||||||
|     const viewType = useNoteViewType(note); |     const viewType = useNoteViewType(note); | ||||||
|     const noteIds = useNoteIds(note, viewType); |     const noteIds = useNoteIds(note, viewType); | ||||||
|     const isEnabled = (!!viewType); |     const isEnabled = (note && !!viewType); | ||||||
|  |  | ||||||
|     // Refresh note Ids |     // Refresh note Ids | ||||||
|     console.log("Got note ids", noteIds); |  | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <div className="note-list-widget"> |         <div className="note-list-widget"> | ||||||
|             {isEnabled && ( |             {isEnabled && ( | ||||||
|                 <div className="note-list-widget-content"> |                 <div className="note-list-widget-content"> | ||||||
|                     {getComponentByViewType(viewType)} |                     {getComponentByViewType(note, noteIds, viewType)} | ||||||
|                 </div> |                 </div> | ||||||
|             )} |             )} | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
| function getComponentByViewType(viewType: ViewTypeOptions) { | function getComponentByViewType(note: FNote, noteIds: string[], viewType: ViewTypeOptions) { | ||||||
|     console.log("Got ", viewType); |     const props: ViewModeProps = { note, noteIds }; | ||||||
|  |      | ||||||
|     switch (viewType) { |     switch (viewType) { | ||||||
|         case "list": |         case "list": | ||||||
|             return <ListView />; |             return <ListView {...props} />; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -54,12 +54,13 @@ function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | | |||||||
|     const [ noteIds, setNoteIds ] = useState<string[]>([]); |     const [ noteIds, setNoteIds ] = useState<string[]>([]); | ||||||
|      |      | ||||||
|     async function refreshNoteIds() { |     async function refreshNoteIds() { | ||||||
|         console.log("Refreshed note IDs"); |  | ||||||
|         if (!note) { |         if (!note) { | ||||||
|             setNoteIds([]); |             setNoteIds([]); | ||||||
|         } else if (viewType === "list" || viewType === "grid") { |         } else if (viewType === "list" || viewType === "grid") { | ||||||
|  |             console.log("Refreshed note IDs"); | ||||||
|             setNoteIds(note.getChildNoteIds()); |             setNoteIds(note.getChildNoteIds()); | ||||||
|         } else { |         } else { | ||||||
|  |             console.log("Refreshed note IDs"); | ||||||
|             setNoteIds(await note.getSubtreeNoteIds()); |             setNoteIds(await note.getSubtreeNoteIds()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,14 @@ | |||||||
|  | import FNote from "../../entities/fnote"; | ||||||
| import type { ViewModeArgs } from "../view_widgets/view_mode"; | import type { ViewModeArgs } from "../view_widgets/view_mode"; | ||||||
|  |  | ||||||
| export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const; | export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const; | ||||||
| export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">; | export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">; | ||||||
| export type ViewTypeOptions = typeof allViewTypes[number]; | export type ViewTypeOptions = typeof allViewTypes[number]; | ||||||
|  |  | ||||||
|  | export interface ViewModeProps { | ||||||
|  |     note: FNote; | ||||||
|  |     /** | ||||||
|  |      * We're using noteIds so that it's not necessary to load all notes at once when paging. | ||||||
|  |      */ | ||||||
|  |     noteIds: string[]; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,14 +1,80 @@ | |||||||
| export default function ListView() { | import { useEffect, useMemo, useState } from "preact/hooks"; | ||||||
|  | import FNote from "../../../entities/fnote"; | ||||||
|  | import Icon from "../../react/Icon"; | ||||||
|  | import { ViewModeProps } from "../interface"; | ||||||
|  | import { useNoteLabel, useNoteLabelBoolean } from "../../react/hooks"; | ||||||
|  | import froca from "../../../services/froca"; | ||||||
|  | import NoteLink from "../../react/NoteLink"; | ||||||
|  |  | ||||||
|  | export default function ListView({ note, noteIds }: 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 } = usePagination(note, filteredNoteIds); | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <div class="note-list"> |         <div class="note-list"> | ||||||
|             <div class="note-list-wrapper"> |             <div class="note-list-wrapper"> | ||||||
|                 List view goes here. |  | ||||||
|                 <div class="note-list-pager"></div> |                 <div class="note-list-pager"></div> | ||||||
|          |          | ||||||
|                 <div class="note-list-container use-tn-links"></div> |                 <div class="note-list-container use-tn-links"> | ||||||
|  |                     {pageNotes?.map(note => ( | ||||||
|  |                         <NoteCard note={note} expand={isExpanded} /> | ||||||
|  |                     ))} | ||||||
|  |                 </div> | ||||||
|          |          | ||||||
|                 <div class="note-list-pager"></div> |                 <div class="note-list-pager"></div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function NoteCard({ note, expand }: { note: FNote, expand?: boolean }) { | ||||||
|  |     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}`; | ||||||
|  |  | ||||||
|  |     return ( | ||||||
|  |         <div | ||||||
|  |             className="note-book-card no-tooltip-preview" | ||||||
|  |             data-note-id={note.noteId} | ||||||
|  |         > | ||||||
|  |             <h5 className="note-book-header"> | ||||||
|  |                 <Icon className="note-icon" icon={note.getIcon()} /> | ||||||
|  |                 <NoteLink notePath={notePath} noPreview showNotePath={isSearch} /> | ||||||
|  |             </h5> | ||||||
|  |         </div> | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function usePagination(note: FNote, noteIds: string[]) { | ||||||
|  |     const [ page, setPage ] = useState(1); | ||||||
|  |     const [ pageNotes, setPageNotes ] = useState<FNote[]>(); | ||||||
|  |  | ||||||
|  |     // Parse page size. | ||||||
|  |     const [ pageSize ] = useNoteLabel(note, "pageSize"); | ||||||
|  |     const pageSizeNum = parseInt(pageSize ?? "", 10); | ||||||
|  |     const normalizedPageSize = (pageSizeNum && pageSizeNum > 0 ? pageSizeNum : 20); | ||||||
|  |  | ||||||
|  |     // Calculate start/end index. | ||||||
|  |     const startIdx = (page - 1) * normalizedPageSize; | ||||||
|  |     const endIdx = startIdx + normalizedPageSize; | ||||||
|  |  | ||||||
|  |     // Obtain notes within the range. | ||||||
|  |     const pageNoteIds = noteIds.slice(startIdx, Math.min(endIdx, noteIds.length)); | ||||||
|  |  | ||||||
|  |     useEffect(() => { | ||||||
|  |         froca.getNotes(pageNoteIds).then(setPageNotes); | ||||||
|  |     }, [ note, noteIds, page, pageSize ]); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         page, | ||||||
|  |         setPage, | ||||||
|  |         pageNotes | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +1,8 @@ | |||||||
| interface IconProps { | interface IconProps { | ||||||
|     icon?: string; |     icon?: string; | ||||||
|  |     className?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default function Icon({ icon }: IconProps) { | export default function Icon({ icon, className }: IconProps) { | ||||||
|     return <span class={icon ?? "bx bx-empty"}></span> |     return <span class={`${icon ?? "bx bx-empty"} ${className ?? ""}`}></span> | ||||||
| } | } | ||||||
| @@ -17,40 +17,10 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|     private showNotePath?: boolean; |     private showNotePath?: boolean; | ||||||
|     private highlightRegex?: RegExp | null; |     private highlightRegex?: RegExp | null; | ||||||
|  |  | ||||||
|     /* |  | ||||||
|      * We're using noteIds so that it's not necessary to load all notes at once when paging |  | ||||||
|      */ |  | ||||||
|     constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { |     constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { | ||||||
|         super(args, viewType); |         super(args, viewType); | ||||||
|         this.$noteList = $(TPL); |         this.$noteList = $(TPL); | ||||||
|  |  | ||||||
|  |  | ||||||
|         args.$parent.append(this.$noteList); |  | ||||||
|  |  | ||||||
|         this.page = 1; |  | ||||||
|         this.pageSize = parseInt(args.parentNote.getLabelValue("pageSize") || ""); |  | ||||||
|  |  | ||||||
|         if (!this.pageSize || this.pageSize < 1) { |  | ||||||
|             this.pageSize = 20; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.$noteList.addClass(`${this.viewType}-view`); |         this.$noteList.addClass(`${this.viewType}-view`); | ||||||
|  |  | ||||||
|         this.showNotePath = args.showNotePath; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** @returns {Set<string>} list of noteIds included (images, included notes) in the parent note and which |  | ||||||
|      *                        don't have to be shown in the note list. */ |  | ||||||
|     getIncludedNoteIds() { |  | ||||||
|         const includedLinks = this.parentNote ? this.parentNote.getRelations().filter((rel) => rel.name === "imageLink" || rel.name === "includeNoteLink") : []; |  | ||||||
|  |  | ||||||
|         return new Set(includedLinks.map((rel) => rel.value)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async beforeRender() { |  | ||||||
|         super.beforeRender(); |  | ||||||
|         const includedNoteIds = this.getIncludedNoteIds(); |  | ||||||
|         this.filteredNoteIds = this.noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renderList() { |     async renderList() { | ||||||
| @@ -70,20 +40,6 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|  |  | ||||||
|         this.$noteList.show(); |         this.$noteList.show(); | ||||||
|  |  | ||||||
|         const $container = this.$noteList.find(".note-list-container").empty(); |  | ||||||
|  |  | ||||||
|         const startIdx = (this.page - 1) * this.pageSize; |  | ||||||
|         const endIdx = startIdx + this.pageSize; |  | ||||||
|  |  | ||||||
|         const pageNoteIds = this.filteredNoteIds.slice(startIdx, Math.min(endIdx, this.filteredNoteIds.length)); |  | ||||||
|         const pageNotes = await froca.getNotes(pageNoteIds); |  | ||||||
|  |  | ||||||
|         for (const note of pageNotes) { |  | ||||||
|             const $card = await this.renderNote(note, this.parentNote.isLabelTruthy("expanded")); |  | ||||||
|  |  | ||||||
|             $container.append($card); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.renderPager(); |         this.renderPager(); | ||||||
|  |  | ||||||
|         return this.$noteList; |         return this.$noteList; | ||||||
| @@ -132,25 +88,15 @@ class ListOrGridView extends ViewMode<{}> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renderNote(note: FNote, expand: boolean = false) { |     async renderNote(note: FNote, expand: boolean = false) { | ||||||
|         const $expander = $('<span class="note-expander bx bx-chevron-right"></span>'); |  | ||||||
|  |  | ||||||
|         const { $renderedAttributes } = await attributeRenderer.renderNormalAttributes(note); |         const { $renderedAttributes } = await attributeRenderer.renderNormalAttributes(note); | ||||||
|         const notePath = |  | ||||||
|             this.parentNote.type === "search" |  | ||||||
|                 ? note.noteId // for search note parent, we want to display a non-search path |  | ||||||
|                 : `${this.parentNote.noteId}/${note.noteId}`; |  | ||||||
|  |  | ||||||
|         const $card = $('<div class="note-book-card">') |         const $card = $('<div class="note-book-card">') | ||||||
|             .attr("data-note-id", note.noteId) |  | ||||||
|             .addClass("no-tooltip-preview") |  | ||||||
|             .append( |             .append( | ||||||
|                 $('<h5 class="note-book-header">') |                 $('<h5 class="note-book-header">') | ||||||
|                     .append($expander) |  | ||||||
|                     .append($('<span class="note-icon">').addClass(note.getIcon())) |  | ||||||
|                     .append( |                     .append( | ||||||
|                         this.viewType === "grid" |                         this.viewType === "grid" | ||||||
|                             ? $('<span class="note-book-title">').text(await treeService.getNoteTitle(note.noteId, this.parentNote.noteId)) |                             ? $('<span class="note-book-title">').text(await treeService.getNoteTitle(note.noteId, this.parentNote.noteId)) | ||||||
|                             : (await linkService.createLink(notePath, { showTooltip: false, showNotePath: this.showNotePath })).addClass("note-book-title") |                             : (await linkService.createLink(notePath, { showNotePath: this.showNotePath })).addClass("note-book-title") | ||||||
|                     ) |                     ) | ||||||
|                     .append($renderedAttributes) |                     .append($renderedAttributes) | ||||||
|             ); |             ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user