import linkService from "./link.js";
import contentRenderer from "./content_renderer.js";
import froca from "./froca.js";
import attributeRenderer from "./attribute_renderer.js";
import libraryLoader from "./library_loader.js";
import treeService from "./tree.js";
import utils from "./utils.js";
import type FNote from "../entities/fnote.js";
const TPL = `
`;
class NoteListRenderer {
private $noteList: JQuery;
private parentNote: FNote;
private noteIds: string[];
private page?: number;
private pageSize?: number;
private viewType?: string | null;
private showNotePath?: boolean;
private highlightRegex?: RegExp | null;
/*
* We're using noteIds so that it's not necessary to load all notes at once when paging
*/
constructor($parent: JQuery, parentNote: FNote, noteIds: string[], showNotePath: boolean = false) {
this.$noteList = $(TPL);
// note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work
$parent.empty();
this.parentNote = parentNote;
const includedNoteIds = this.getIncludedNoteIds();
this.noteIds = noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden");
if (this.noteIds.length === 0) {
return;
}
$parent.append(this.$noteList);
this.page = 1;
this.pageSize = parseInt(parentNote.getLabelValue("pageSize") || "");
if (!this.pageSize || this.pageSize < 1) {
this.pageSize = 20;
}
this.viewType = parentNote.getLabelValue("viewType");
if (!["list", "grid"].includes(this.viewType || "")) {
// when not explicitly set, decide based on the note type
this.viewType = parentNote.type === "search" ? "list" : "grid";
}
this.$noteList.addClass(`${this.viewType}-view`);
this.showNotePath = showNotePath;
}
/** @returns {Set} 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 renderList() {
if (this.noteIds.length === 0 || !this.page || !this.pageSize) {
this.$noteList.hide();
return;
}
const highlightedTokens = this.parentNote.highlightedTokens || [];
if (highlightedTokens.length > 0) {
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
const regex = highlightedTokens.map((token) => utils.escapeRegExp(token)).join("|");
this.highlightRegex = new RegExp(regex, "gi");
} else {
this.highlightRegex = null;
}
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.noteIds.slice(startIdx, Math.min(endIdx, this.noteIds.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();
return this.$noteList;
}
renderPager() {
const $pager = this.$noteList.find(".note-list-pager").empty();
if (!this.page || !this.pageSize) {
return;
}
const pageCount = Math.ceil(this.noteIds.length / this.pageSize);
$pager.toggle(pageCount > 1);
let lastPrinted;
for (let i = 1; i <= pageCount; i++) {
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(this.page - i) <= 2) {
lastPrinted = true;
const startIndex = (i - 1) * this.pageSize + 1;
const endIndex = Math.min(this.noteIds.length, i * this.pageSize);
$pager.append(
i === this.page
? $("").text(i).css("text-decoration", "underline").css("font-weight", "bold")
: $('')
.text(i)
.attr("title", `Page of ${startIndex} - ${endIndex}`)
.on("click", () => {
this.page = i;
this.renderList();
}),
" "
);
} else if (lastPrinted) {
$pager.append("... ");
lastPrinted = false;
}
}
// no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all
$pager.append(``);
}
async renderNote(note: FNote, expand: boolean = false) {
const $expander = $('');
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 = $('')
.attr("data-note-id", note.noteId)
.append(
$('