mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-06-28 05:52:07 +02:00
#749 - Filter setting
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
### ✨ New features
|
||||
|
||||
- [#731](https://github.com/estruyf/vscode-front-matter/issues/731): Added the ability to map/unmap taxonomy to multiple pages at once
|
||||
- [#749](https://github.com/estruyf/vscode-front-matter/issues/749): Ability to set your own filters on the content dashboard with the `frontMatter.content.filters` setting
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
|
||||
@@ -492,6 +492,29 @@
|
||||
"markdownDescription": "%setting.frontMatter.content.wysiwyg.markdownDescription%",
|
||||
"scope": "Content"
|
||||
},
|
||||
"frontMatter.content.filters": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"pageFolders", "tags", "categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"frontMatter.custom.scripts": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"setting.frontMatter.content.sorting.items.properties.type.description": "Type of the field value",
|
||||
"setting.frontMatter.content.supportedFileTypes.markdownDescription": "Specify the file types that you want to use in Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.supportedfiletypes)",
|
||||
"setting.frontMatter.content.wysiwyg.markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.wysiwyg)",
|
||||
"setting.frontMatter.content.filters.markdownDescription": "Specify the filters you want to use for your content dashboard. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.content.filters)",
|
||||
"setting.frontMatter.custom.scripts.markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument. [Check in the docs](https://frontmatter.codes/docs/settings/overview#frontmatter.custom.scripts)",
|
||||
"setting.frontMatter.custom.scripts.items.properties.id.description": "ID of the script.",
|
||||
"setting.frontMatter.custom.scripts.items.properties.title.description": "Title you want to give to your script. Will be shown as the title of the button.",
|
||||
|
||||
@@ -60,6 +60,7 @@ export const SETTING_CONTENT_STATIC_FOLDER = 'content.publicFolder';
|
||||
export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = 'content.fmHighlight';
|
||||
export const SETTING_CONTENT_DRAFT_FIELD = 'content.draftField';
|
||||
export const SETTING_CONTENT_SORTING = 'content.sorting';
|
||||
export const SETTING_CONTENT_FILTERS = 'content.filters';
|
||||
export const SETTING_CONTENT_WYSIWYG = 'content.wysiwyg';
|
||||
export const SETTING_CONTENT_PLACEHOLDERS = 'content.placeholders';
|
||||
export const SETTING_CONTENT_SNIPPETS = 'content.snippets';
|
||||
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
TagAtom,
|
||||
CategoryAtom,
|
||||
DEFAULT_TAG_STATE,
|
||||
DEFAULT_CATEGORY_STATE
|
||||
DEFAULT_CATEGORY_STATE,
|
||||
FiltersAtom
|
||||
} from '../../state';
|
||||
import { DefaultValue } from 'recoil';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
@@ -33,11 +34,13 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
const filters = useRecoilValue(FiltersAtom);
|
||||
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const resetFolder = useResetRecoilState(FolderAtom);
|
||||
const resetTag = useResetRecoilState(TagAtom);
|
||||
const resetCategory = useResetRecoilState(CategoryAtom);
|
||||
const resetFilters = useResetRecoilState(FiltersAtom);
|
||||
|
||||
const reset = () => {
|
||||
setShow(false);
|
||||
@@ -45,19 +48,26 @@ export const ClearFilters: React.FunctionComponent<IClearFiltersProps> = (
|
||||
resetFolder();
|
||||
resetTag();
|
||||
resetCategory();
|
||||
resetFilters();
|
||||
};
|
||||
|
||||
const hasCustomFilters = useMemo(() => {
|
||||
const names = Object.keys(filters);
|
||||
return names.some((name) => filters[name]);
|
||||
}, [filters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
folder !== DEFAULT_FOLDER_STATE ||
|
||||
tag !== DEFAULT_TAG_STATE ||
|
||||
category !== DEFAULT_CATEGORY_STATE
|
||||
category !== DEFAULT_CATEGORY_STATE ||
|
||||
hasCustomFilters
|
||||
) {
|
||||
setShow(true);
|
||||
} else {
|
||||
setShow(false);
|
||||
}
|
||||
}, [folder, tag, category]);
|
||||
}, [folder, tag, category, hasCustomFilters]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import * as React from 'react';
|
||||
import { FoldersFilter } from './FoldersFilter';
|
||||
import { Filter } from './Filter';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { CategoryAtom, SettingsSelector, TagAtom, FiltersAtom, FilterValuesAtom } from '../../state';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { firstToUpper } from '../../../helpers/StringHelpers';
|
||||
|
||||
export interface IFiltersProps { }
|
||||
|
||||
export const Filters: React.FunctionComponent<IFiltersProps> = (_: React.PropsWithChildren<IFiltersProps>) => {
|
||||
const [crntFilters, setCrntFilters] = useRecoilState(FiltersAtom);
|
||||
const [crntTag, setCrntTag] = useRecoilState(TagAtom);
|
||||
const [crntCategory, setCrntCategory] = useRecoilState(CategoryAtom);
|
||||
const filterValues = useRecoilValue(FilterValuesAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
const otherFilters = useMemo(() => settings?.filters?.filter((filter) => filter !== "pageFolders" && filter !== "tags" && filter !== "categories"), [settings?.filters]);
|
||||
|
||||
const otherFilterValues = useMemo(() => {
|
||||
return otherFilters?.map((filter) => {
|
||||
const filterName = typeof filter === "string" ? filter : filter.name;
|
||||
const filterTitle = typeof filter === "string" ? firstToUpper(filter) : filter.title;
|
||||
const values = filterValues?.[filterName];
|
||||
if (!values || values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Filter
|
||||
key={filterName}
|
||||
label={filterTitle}
|
||||
activeItem={crntFilters[filterName]}
|
||||
items={values}
|
||||
onClick={(value) => setCrntFilters((prev) => {
|
||||
let clone = Object.assign({}, prev);
|
||||
if (!clone[filterName] && value) {
|
||||
clone[filterName] = value;
|
||||
} else {
|
||||
clone[filterName] = value || "";
|
||||
}
|
||||
return clone;
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [otherFilters, crntFilters, filterValues, setCrntFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.search) {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const taxonomy = searchParams.get('taxonomy');
|
||||
const value = searchParams.get('value');
|
||||
|
||||
if (taxonomy && value) {
|
||||
if (taxonomy === 'tags') {
|
||||
setCrntTag(value);
|
||||
} else if (taxonomy === 'categories') {
|
||||
setCrntCategory(value);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setCrntFilters({});
|
||||
|
||||
setCrntTag('');
|
||||
setCrntCategory('');
|
||||
}, [location.search]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
settings?.filters?.includes("pageFolders") && (
|
||||
<FoldersFilter />
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
settings?.filters?.includes("tags") && (
|
||||
<Filter
|
||||
label={`Tag`}
|
||||
activeItem={crntTag}
|
||||
items={settings?.tags || []}
|
||||
onClick={(value) => setCrntTag(value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
settings?.filters?.includes("categories") && (
|
||||
<Filter
|
||||
label={`Category`}
|
||||
activeItem={crntCategory}
|
||||
items={settings?.categories || []}
|
||||
onClick={(value) => setCrntCategory(value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{otherFilterValues}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -6,11 +6,11 @@ import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface IFoldersProps { }
|
||||
export interface IFoldersFilterProps { }
|
||||
|
||||
export const Folders: React.FunctionComponent<
|
||||
IFoldersProps
|
||||
> = ({ }: React.PropsWithChildren<IFoldersProps>) => {
|
||||
export const FoldersFilter: React.FunctionComponent<
|
||||
IFoldersFilterProps
|
||||
> = ({ }: React.PropsWithChildren<IFoldersFilterProps>) => {
|
||||
const DEFAULT_TYPE = l10n.t(LocalizationKey.dashboardHeaderFoldersDefault);
|
||||
const [crntFolder, setCrntFolder] = useRecoilState(FolderAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
@@ -1,14 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Sorting } from './Sorting';
|
||||
import { Searchbox } from './Searchbox';
|
||||
import { Filter } from './Filter';
|
||||
import { Folders } from './Folders';
|
||||
import { Settings, NavigationType } from '../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { CategoryAtom, GroupingSelector, SortingAtom, TagAtom } from '../../state';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { GroupingSelector, SortingAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
|
||||
@@ -33,6 +31,7 @@ import { LocalizationKey } from '../../../localization';
|
||||
import { SettingsLink } from '../SettingsView/SettingsLink';
|
||||
import { Link } from '../Common/Link';
|
||||
import { SPONSOR_LINK } from '../../../constants';
|
||||
import { Filters } from './Filters';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -50,8 +49,6 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
totalPages,
|
||||
settings
|
||||
}: React.PropsWithChildren<IHeaderProps>) => {
|
||||
const [crntTag, setCrntTag] = useRecoilState(TagAtom);
|
||||
const [crntCategory, setCrntCategory] = useRecoilState(CategoryAtom);
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const location = useLocation();
|
||||
@@ -123,27 +120,6 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
return [];
|
||||
}, [settings?.dashboardState?.contents?.templatesEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.search) {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const taxonomy = searchParams.get('taxonomy');
|
||||
const value = searchParams.get('value');
|
||||
|
||||
if (taxonomy && value) {
|
||||
if (taxonomy === 'tags') {
|
||||
setCrntTag(value);
|
||||
} else if (taxonomy === 'categories') {
|
||||
setCrntCategory(value);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setCrntTag('');
|
||||
setCrntCategory('');
|
||||
}, [location.search]);
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
||||
<div className={`mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
@@ -214,21 +190,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
>
|
||||
<ClearFilters />
|
||||
|
||||
<Folders />
|
||||
|
||||
<Filter
|
||||
label={`Tag`}
|
||||
activeItem={crntTag}
|
||||
items={settings?.tags || []}
|
||||
onClick={(value) => setCrntTag(value)}
|
||||
/>
|
||||
|
||||
<Filter
|
||||
label={`Category`}
|
||||
activeItem={crntCategory}
|
||||
items={settings?.categories || []}
|
||||
onClick={(value) => setCrntCategory(value)}
|
||||
/>
|
||||
<Filters />
|
||||
|
||||
<Grouping />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './Filter';
|
||||
export * from './Folders';
|
||||
export * from './FoldersFilter';
|
||||
export * from './Grouping';
|
||||
export * from './Header';
|
||||
export * from './Searchbox';
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import {
|
||||
AllPagesAtom,
|
||||
CategorySelector,
|
||||
FilterValuesAtom,
|
||||
FiltersAtom,
|
||||
FolderSelector,
|
||||
SearchSelector,
|
||||
SettingsSelector,
|
||||
@@ -26,12 +28,14 @@ export default function usePages(pages: Page[]) {
|
||||
const [sortedPages, setSortedPages] = useState<Page[]>([]);
|
||||
const [sorting, setSorting] = useRecoilState(SortingAtom);
|
||||
const [tabInfo, setTabInfo] = useRecoilState(TabInfoAtom);
|
||||
const [, setFilterValues] = useRecoilState(FilterValuesAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const tab = useRecoilValue(TabSelector);
|
||||
const folder = useRecoilValue(FolderSelector);
|
||||
const search = useRecoilValue(SearchSelector);
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
const filters = useRecoilValue(FiltersAtom);
|
||||
|
||||
/**
|
||||
* Process all the pages by applying the sorting, filtering and searching.
|
||||
@@ -86,9 +90,19 @@ export default function usePages(pages: Page[]) {
|
||||
);
|
||||
}
|
||||
|
||||
const filterNames = Object.keys(filters);
|
||||
if (filterNames.length > 0) {
|
||||
for (const filter of filterNames) {
|
||||
const filterValue = filters[filter];
|
||||
if (filterValue) {
|
||||
pagesSorted = pagesSorted.filter((page) => page[filter] === filterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSortedPages(pagesSorted);
|
||||
},
|
||||
[settings, tab, folder, search, tag, category, sorting, tabInfo]
|
||||
[settings, tab, folder, search, tag, category, sorting, tabInfo, filters]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -159,10 +173,27 @@ export default function usePages(pages: Page[]) {
|
||||
// Set the tab information
|
||||
setTabInfo(draftTypes);
|
||||
|
||||
if (Object.keys(filters).length === 0) {
|
||||
const availableFilters = (settings?.filters || []).filter((f) => f !== 'pageFolders' && f !== 'tags' && f !== 'categories');
|
||||
if (availableFilters.length > 0) {
|
||||
const allFilters: { [filter: string]: string[]; } = {};
|
||||
for (const filter of availableFilters) {
|
||||
if (filter) {
|
||||
const filterName = typeof filter === 'string' ? filter : filter.name;
|
||||
const values = crntPages.map((page) => page[filterName]).filter((value) => value);
|
||||
allFilters[filterName] = [...new Set(values)];
|
||||
}
|
||||
}
|
||||
setFilterValues(allFilters);
|
||||
} else {
|
||||
setFilterValues({});
|
||||
}
|
||||
}
|
||||
|
||||
// Set the pages
|
||||
setPageItems(crntPages);
|
||||
},
|
||||
[tab, tabInfo, settings]
|
||||
[tab, tabInfo, settings, filters]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -204,7 +235,7 @@ export default function usePages(pages: Page[]) {
|
||||
} else {
|
||||
startPageProcessing();
|
||||
}
|
||||
}, [settings?.draftField, pages, sorting, search, tag, category, folder]);
|
||||
}, [settings?.draftField, pages, sorting, search, tag, category, filters, folder]);
|
||||
|
||||
useEffect(() => {
|
||||
processByTab(sortedPages);
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface Settings {
|
||||
framework: Framework | null | undefined;
|
||||
draftField: DraftField | null | undefined;
|
||||
customSorting: SortingSetting[] | undefined;
|
||||
filters: (string | { title: string; name: string })[] | undefined;
|
||||
dashboardState: DashboardState;
|
||||
scripts: CustomScript[];
|
||||
dataFiles: DataFile[] | undefined;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const FilterValuesAtom = atom<{ [filter: string]: string[] }>({
|
||||
key: 'FilterValuesAtom',
|
||||
default: {}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const FiltersAtom = atom<{ [filter: string]: string }>({
|
||||
key: 'FiltersAtom',
|
||||
default: {}
|
||||
});
|
||||
@@ -3,6 +3,8 @@ export * from './AllPagesAtom';
|
||||
export * from './AllStaticFoldersAtom';
|
||||
export * from './CategoryAtom';
|
||||
export * from './DashboardViewAtom';
|
||||
export * from './FilterValuesAtom';
|
||||
export * from './FiltersAtom';
|
||||
export * from './FolderAtom';
|
||||
export * from './GroupingAtom';
|
||||
export * from './LightboxAtom';
|
||||
@@ -11,6 +13,7 @@ export * from './MediaFoldersAtom';
|
||||
export * from './MediaTotalAtom';
|
||||
export * from './ModeAtom';
|
||||
export * from './PageAtom';
|
||||
export * from './PinnedItems';
|
||||
export * from './SearchAtom';
|
||||
export * from './SearchReadyAtom';
|
||||
export * from './SelectedMediaFolderAtom';
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CONTEXT,
|
||||
ExtensionState,
|
||||
SETTING_CONTENT_DRAFT_FIELD,
|
||||
SETTING_CONTENT_FILTERS,
|
||||
SETTING_CONTENT_SORTING,
|
||||
SETTING_CONTENT_SORTING_DEFAULT,
|
||||
SETTING_DASHBOARD_OPENONSTART,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
SETTING_FRAMEWORK_ID,
|
||||
SETTING_MEDIA_SORTING_DEFAULT,
|
||||
SETTING_CUSTOM_SCRIPTS,
|
||||
SETTING_TAXONOMY_CONTENT_TYPES,
|
||||
SETTING_CONTENT_SNIPPETS,
|
||||
SETTING_DATE_FORMAT,
|
||||
SETTING_DASHBOARD_CONTENT_TAGS,
|
||||
@@ -106,6 +106,7 @@ export class DashboardSettings {
|
||||
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
|
||||
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
|
||||
contentFolders: Folders.get(),
|
||||
filters: Settings.get<string[]>(SETTING_CONTENT_FILTERS),
|
||||
crntFramework: Settings.get<string>(SETTING_FRAMEWORK_ID),
|
||||
framework: !isInitialized && wsFolder ? await FrameworkDetector.get(wsFolder.fsPath) : null,
|
||||
scripts: Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [],
|
||||
|
||||
Reference in New Issue
Block a user