diff --git a/.vscode/settings.json b/.vscode/settings.json index 647e1f4e..ebaa23de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,12 @@ "description": "Panel styles", "type": "file", "groupId": "panel" + }, + { + "name": "settings.ts", + "path": "src/constants/settings.ts", + "description": "Settings names", + "type": "file" } ], } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fafe24c9..cd0a4a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#376](https://github.com/estruyf/vscode-front-matter/issues/376): Ability to run scripts after content was created - [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote) - [#379](https://github.com/estruyf/vscode-front-matter/issues/377): New `frontMatter.config.reload` command to reload the configuration file + reinitialize its listeners +- [#401](https://github.com/estruyf/vscode-front-matter/issues/401): Content dashboard now has pagination enabled and can be disabled via the `frontMatter.dashboard.content.pagination` setting ### 🎨 Enhancements diff --git a/package.json b/package.json index 100edcf9..18c23b45 100644 --- a/package.json +++ b/package.json @@ -445,6 +445,12 @@ }, "scope": "Custom scripts" }, + "frontMatter.dashboard.content.pagination": { + "type": "boolean", + "default": true, + "markdownDescription": "Specify if you want to enable/disable pagination for your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.content.pagination)", + "scope": "Dashboard" + }, "frontMatter.dashboard.content.cardTags": { "type": "string", "default": "tags", diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 4bcc196a..e8f7edd0 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -66,6 +66,7 @@ export const SETTING_MEDIA_SUPPORTED_MIMETYPES = "media.supportedMimeTypes"; export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTING_DASHBOARD_CONTENT_TAGS = "dashboard.content.cardTags"; +export const SETTING_DASHBOARD_CONTENT_PAGINATION = "dashboard.content.pagination"; export const SETTING_DATA_FILES = "data.files"; export const SETTING_DATA_FOLDERS = "data.folders"; diff --git a/src/dashboardWebView/components/Contents/Overview.tsx b/src/dashboardWebView/components/Contents/Overview.tsx index cb7637e6..b83f1c7b 100644 --- a/src/dashboardWebView/components/Contents/Overview.tsx +++ b/src/dashboardWebView/components/Contents/Overview.tsx @@ -1,14 +1,15 @@ import { Disclosure } from '@headlessui/react'; import {ChevronRightIcon} from '@heroicons/react/solid'; import * as React from 'react'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { groupBy } from '../../../helpers/GroupBy'; import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon'; import { GroupOption } from '../../constants/GroupOption'; import { Page } from '../../models/Page'; import { Settings } from '../../models/Settings'; -import { GroupingSelector } from '../../state'; +import { GroupingSelector, PageAtom } from '../../state'; +import { PAGE_LIMIT } from '../Header/Pagination'; import { Item } from './Item'; import { List } from './List'; @@ -19,6 +20,15 @@ export interface IOverviewProps { export const Overview: React.FunctionComponent = ({pages, settings}: React.PropsWithChildren) => { const grouping = useRecoilValue(GroupingSelector); + const page = useRecoilValue(PageAtom); + + const pagedPages = useMemo(() => { + if (settings?.dashboardState.contents.pagination) { + return pages.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT)); + } + + return pages; + }, [pages, page, settings]); const groupName = useCallback((groupId, groupedPages) => { if (grouping === GroupOption.Draft) { @@ -89,7 +99,7 @@ export const Overview: React.FunctionComponent = ({pages, settin return ( - {pages.map((page, idx) => ( + {pagedPages.map((page, idx) => ( ))} diff --git a/src/dashboardWebView/components/Header/Grouping.tsx b/src/dashboardWebView/components/Header/Grouping.tsx index e245fbd6..0e8acb01 100644 --- a/src/dashboardWebView/components/Header/Grouping.tsx +++ b/src/dashboardWebView/components/Header/Grouping.tsx @@ -7,7 +7,7 @@ import { MenuButton, MenuItem, MenuItems } from '../Menu'; export interface IGroupingProps {} -export const groupOptions = [ +export const GROUP_OPTIONS = [ { name: "None", id: GroupOption.none }, { name: "Year", id: GroupOption.Year }, { name: "Draft/Published", id: GroupOption.Draft }, @@ -16,7 +16,7 @@ export const groupOptions = [ export const Grouping: React.FunctionComponent = ({}: React.PropsWithChildren) => { const [ group, setGroup ] = useRecoilState(GroupingAtom); - const crntGroup = groupOptions.find(x => x.id === group); + const crntGroup = GROUP_OPTIONS.find(x => x.id === group); return (
@@ -24,7 +24,7 @@ export const Grouping: React.FunctionComponent = ({}: React.Prop - {groupOptions.map((option) => ( + {GROUP_OPTIONS.map((option) => ( = ({header, totalPages, folders, settings }: React.PropsWithChildren) => { const [ crntTag, setCrntTag ] = useRecoilState(TagAtom); const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom); + const grouping = useRecoilValue(GroupingSelector); const resetSorting = useResetRecoilState(SortingAtom); const location = useLocation(); const navigate = useNavigate(); @@ -175,6 +178,14 @@ export const Header: React.FunctionComponent = ({header, totalPage
+ + { + (settings?.dashboardState.contents.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && ( +
+ +
+ ) + } ) } diff --git a/src/dashboardWebView/components/Header/Pagination.tsx b/src/dashboardWebView/components/Header/Pagination.tsx index 85dbdda1..3b52baf3 100644 --- a/src/dashboardWebView/components/Header/Pagination.tsx +++ b/src/dashboardWebView/components/Header/Pagination.tsx @@ -1,16 +1,29 @@ import * as React from 'react'; +import { useEffect, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { LIMIT } from '../../hooks/useMedia'; +import { routePaths } from '../..'; import { MediaTotalSelector, PageAtom } from '../../state'; import { PaginationButton } from './PaginationButton'; -export interface IPaginationProps {} +export interface IPaginationProps { + totalPages?: number; +} -export const Pagination: React.FunctionComponent = (props: React.PropsWithChildren) => { +export const PAGE_LIMIT = 16; + +export const Pagination: React.FunctionComponent = ({ totalPages }: React.PropsWithChildren) => { const [ page, setPage ] = useRecoilState(PageAtom); const totalMedia = useRecoilValue(MediaTotalSelector); + const location = useLocation(); - const totalPages = Math.ceil(totalMedia / LIMIT) - 1; + const totalItems: number = useMemo(() => { + if (location.pathname === routePaths.contents) { + return Math.ceil((totalPages || 0) / PAGE_LIMIT) - 1 + } else { + return Math.ceil(totalMedia / PAGE_LIMIT) - 1; + } + }, [location.pathname, totalPages, totalMedia]); const getButtons = (): number[] => { const maxButtons = 5; @@ -19,12 +32,16 @@ export const Pagination: React.FunctionComponent = (props: Rea const end = page + maxButtons; for (let i = start; i <= end; i++) { - if (i >= 0 && i <= totalPages) { + if (i >= 0 && i <= totalItems) { buttons.push(i); } } return buttons; }; + + useEffect(() => { + setPage(0); + }, []); return (
@@ -54,19 +71,19 @@ export const Pagination: React.FunctionComponent = (props: Rea setPage(button) } } - className={`${page === button ? 'bg-gray-200 px-2 text-vulcan-500' : 'text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500'} max-h-8`} + className={`${page === button ? 'bg-gray-200 px-2 text-vulcan-500' : 'text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500'} max-h-8 rounded-sm`} >{button + 1} ))} = totalPages} + disabled={page >= totalItems} onClick={() => setPage(page + 1)} /> = totalPages} - onClick={() => setPage(totalPages)} /> + disabled={page >= totalItems} + onClick={() => setPage(totalItems)} />
); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/PaginationStatus.tsx b/src/dashboardWebView/components/Header/PaginationStatus.tsx index ad5479af..b145e18e 100644 --- a/src/dashboardWebView/components/Header/PaginationStatus.tsx +++ b/src/dashboardWebView/components/Header/PaginationStatus.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { useRecoilValue } from 'recoil'; import { MediaTotalSelector, PageAtom } from '../../state'; -import { LIMIT } from '../../hooks/useMedia'; +import { PAGE_LIMIT } from './Pagination'; export interface IPaginationStatusProps {} @@ -10,7 +10,7 @@ export const PaginationStatus: React.FunctionComponent = const page = useRecoilValue(PageAtom); const getTotalPage = () => { - const mediaItems = ((page + 1) * LIMIT); + const mediaItems = ((page + 1) * PAGE_LIMIT); if (totalMedia < mediaItems) { return totalMedia; } @@ -20,7 +20,7 @@ export const PaginationStatus: React.FunctionComponent = return (

- Showing {(page * LIMIT) + 1} to {getTotalPage()} of{' '} + Showing {(page * PAGE_LIMIT) + 1} to {getTotalPage()} of{' '} {totalMedia} results

diff --git a/src/dashboardWebView/hooks/useMedia.tsx b/src/dashboardWebView/hooks/useMedia.tsx index 170848fe..95fcd21c 100644 --- a/src/dashboardWebView/hooks/useMedia.tsx +++ b/src/dashboardWebView/hooks/useMedia.tsx @@ -4,8 +4,9 @@ import { useState, useEffect, useCallback } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { MediaInfo, MediaPaths } from '../../models'; import { DashboardCommand } from '../DashboardCommand'; -import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SearchSelector, SelectedMediaFolderAtom } from '../state'; +import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SelectedMediaFolderAtom } from '../state'; import Fuse from 'fuse.js'; +import { PAGE_LIMIT } from '../components/Header/Pagination'; const fuseOptions: Fuse.IFuseOptions = { keys: [ @@ -18,11 +19,9 @@ const fuseOptions: Fuse.IFuseOptions = { includeScore: true }; -export const LIMIT = 16; - export default function useMedia() { const [ media, setMedia ] = useState([]); - const [ page, setPage ] = useRecoilState(PageAtom); + const page = useRecoilValue(PageAtom); const [ searchedMedia, setSearchedMedia ] = useState([]); const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom); const [ , setTotal ] = useRecoilState(MediaTotalAtom); @@ -31,7 +30,7 @@ export default function useMedia() { const search = useRecoilValue(SearchAtom); const getMedia = useCallback(() => { - return searchedMedia.slice(page * LIMIT, ((page + 1) * LIMIT)); + return searchedMedia.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT)); }, [searchedMedia, page]); const messageListener = (message: MessageEvent>) => { diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 7add77bf..ee1ab44e 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -44,6 +44,7 @@ export interface ContentsViewState { defaultSorting: string | null | undefined; tags: string | null | undefined; templatesEnabled: boolean | null | undefined; + pagination: boolean | null | undefined; } export interface MediaViewState extends ContentsViewState { diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 4afbcfc6..58357923 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -3,7 +3,7 @@ import { basename, join } from "path"; import { workspace } from "vscode"; import { Folders } from "../commands/Folders"; import { Project } from "../commands/Project"; -import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED, SETTING_GIT_ENABLED } from "../constants"; +import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED, SETTING_GIT_ENABLED, SETTING_DASHBOARD_CONTENT_PAGINATION } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; @@ -22,6 +22,7 @@ export class DashboardSettings { const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = Project.isInitialized(); const gitActions = Settings.get(SETTING_GIT_ENABLED); + const pagination = Settings.get(SETTING_DASHBOARD_CONTENT_PAGINATION) return { git: { @@ -53,7 +54,8 @@ export class DashboardSettings { sorting: await ext.getState(ExtensionState.Dashboard.Contents.Sorting, "workspace"), defaultSorting: Settings.get(SETTING_CONTENT_SORTING_DEFAULT), tags: Settings.get(SETTING_DASHBOARD_CONTENT_TAGS), - templatesEnabled: Settings.get(SETTING_TEMPLATES_ENABLED) + templatesEnabled: Settings.get(SETTING_TEMPLATES_ENABLED), + pagination: pagination !== undefined ? pagination : true }, media: { sorting: await ext.getState(ExtensionState.Dashboard.Media.Sorting, "workspace"),