mirror of
https://github.com/zadam/trilium.git
synced 2026-02-19 12:14:23 +01:00
150 lines
5.4 KiB
TypeScript
150 lines
5.4 KiB
TypeScript
import { ComponentChildren } from "preact";
|
|
import { Dispatch, StateUpdater, useEffect, useState } from "preact/hooks";
|
|
import FNote from "../../entities/fnote";
|
|
import froca from "../../services/froca";
|
|
import { useNoteLabelInt } from "../react/hooks";
|
|
import { t } from "../../services/i18n";
|
|
import ActionButton from "../react/ActionButton";
|
|
import Button from "../react/Button";
|
|
import "./Pagination.css";
|
|
import clsx from "clsx";
|
|
|
|
interface PaginationContext {
|
|
page: number;
|
|
setPage: Dispatch<StateUpdater<number>>;
|
|
pageNotes?: FNote[];
|
|
pageCount: number;
|
|
pageSize: number;
|
|
totalNotes: number;
|
|
}
|
|
|
|
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
|
|
if (pageCount < 2) return;
|
|
|
|
return (
|
|
<div class="note-list-pager-container">
|
|
<div class="note-list-pager">
|
|
<ActionButton
|
|
icon="bx bx-chevron-left"
|
|
className="note-list-pager-nav-button"
|
|
disabled={(page === 1)}
|
|
text={t("pagination.prev_page")}
|
|
onClick={() => setPage(page - 1)}
|
|
/>
|
|
|
|
<PageButtons page={page} setPage={setPage} pageCount={pageCount} />
|
|
<div className="note-list-pager-narrow-counter">
|
|
<strong>{page}</strong> / <strong>{pageCount}</strong>
|
|
</div>
|
|
|
|
<ActionButton
|
|
icon="bx bx-chevron-right"
|
|
className="note-list-pager-nav-button"
|
|
disabled={(page === pageCount)}
|
|
text={t("pagination.next_page")}
|
|
onClick={() => setPage(page + 1)}
|
|
/>
|
|
|
|
<div className="note-list-pager-total-count">
|
|
{t("pagination.total_notes", { count: totalNotes })}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface PageButtonsProps {
|
|
page: number;
|
|
setPage: Dispatch<StateUpdater<number>>;
|
|
pageCount: number;
|
|
}
|
|
|
|
function PageButtons(props: PageButtonsProps) {
|
|
const maxButtonCount = 9;
|
|
const maxLeftRightSegmentLength = 2;
|
|
|
|
// The left-side segment
|
|
const leftLength = Math.min(props.pageCount, maxLeftRightSegmentLength);
|
|
const leftStart = 1;
|
|
|
|
// The middle segment
|
|
const middleMaxLength = maxButtonCount - maxLeftRightSegmentLength * 2;
|
|
const middleLength = Math.min(props.pageCount - leftLength, middleMaxLength);
|
|
let middleStart = props.page - Math.floor(middleLength / 2);
|
|
middleStart = Math.max(middleStart, leftLength + 1);
|
|
|
|
// The right-side segment
|
|
const rightLength = Math.min(props.pageCount - (middleLength + leftLength), maxLeftRightSegmentLength);
|
|
const rightStart = props.pageCount - rightLength + 1;
|
|
middleStart = Math.min(middleStart, rightStart - middleLength);
|
|
|
|
const totalButtonCount = leftLength + middleLength + rightLength;
|
|
const hasLeadingEllipsis = (middleStart - leftLength > 1);
|
|
const hasTrailingEllipsis = (rightStart - (middleStart + middleLength - 1) > 1);
|
|
|
|
return <div className={clsx("note-list-pager-page-button-container", {
|
|
"note-list-pager-ellipsis-present": (totalButtonCount === maxButtonCount)
|
|
})}
|
|
style={{"--note-list-pager-page-button-count": totalButtonCount}}>
|
|
{[
|
|
...createSegment(leftStart, leftLength, props.page, props.setPage, false),
|
|
...createSegment(middleStart, middleLength, props.page, props.setPage, hasLeadingEllipsis),
|
|
...createSegment(rightStart, rightLength, props.page, props.setPage, hasTrailingEllipsis),
|
|
]}
|
|
</div>;
|
|
}
|
|
|
|
function createSegment(start: number, length: number, currentPage: number, setPage: Dispatch<StateUpdater<number>>, prependEllipsis: boolean): ComponentChildren[] {
|
|
const children: ComponentChildren[] = [];
|
|
|
|
if (prependEllipsis) {
|
|
children.push(<span class="note-list-pager-ellipsis">...</span>);
|
|
}
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
const pageNum = start + i;
|
|
const isCurrent = (pageNum === currentPage);
|
|
children.push((
|
|
<Button
|
|
text={pageNum.toString()}
|
|
kind="lowProfile"
|
|
className={clsx(
|
|
"note-list-pager-page-button",
|
|
{"note-list-pager-page-button-current": isCurrent}
|
|
)}
|
|
disabled={isCurrent}
|
|
onClick={() => setPage(pageNum)}
|
|
/>
|
|
));
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
|
|
const [ page, setPage ] = useState(1);
|
|
const [ pageNotes, setPageNotes ] = useState<FNote[]>();
|
|
|
|
// Parse page size.
|
|
const [ pageSize ] = useNoteLabelInt(note, "pageSize");
|
|
const normalizedPageSize = (pageSize && pageSize > 0 ? pageSize : 20);
|
|
|
|
// Calculate start/end index.
|
|
const startIdx = (page - 1) * normalizedPageSize;
|
|
const endIdx = startIdx + normalizedPageSize;
|
|
const pageCount = Math.ceil(noteIds.length / 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, pageCount,
|
|
pageSize: normalizedPageSize,
|
|
totalNotes: noteIds.length
|
|
};
|
|
}
|