mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-10 02:42:55 +02:00
Merge branch 'issue/431' into dev
This commit is contained in:
@@ -68,11 +68,9 @@ describe("Initialization testing", function() {
|
||||
|
||||
async function notificationExists(workbench: Workbench, text: string): Promise<Notification | undefined> {
|
||||
const notifications = await (await (new StatusBar()).openNotificationsCenter()).getNotifications(NotificationType.Info);
|
||||
console.log(`Notifications:`, notifications.length);
|
||||
|
||||
for (const notification of notifications) {
|
||||
const message = await notification.getMessage();
|
||||
console.log(message)
|
||||
if (message.indexOf(text) >= 0) {
|
||||
return notification;
|
||||
}
|
||||
|
||||
+7
-2
@@ -447,9 +447,9 @@
|
||||
"scope": "Custom scripts"
|
||||
},
|
||||
"frontMatter.dashboard.content.pagination": {
|
||||
"type": "boolean",
|
||||
"type": ["boolean", "number"],
|
||||
"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)",
|
||||
"markdownDescription": "Specify if you want to enable/disable pagination for your content. You can define your page number up to 52. Default items per page is `16`. Disabling the pagination can be done by setting it to `false`. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.content.pagination)",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.dashboard.content.cardTags": {
|
||||
@@ -1742,6 +1742,11 @@
|
||||
"command": "frontMatter.git.sync",
|
||||
"title": "Sync",
|
||||
"category": "Front Matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.cache.clear",
|
||||
"title": "Clear cache",
|
||||
"category": "Front Matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { commands } from "vscode";
|
||||
import { COMMAND_NAME, ExtensionState } from "../constants";
|
||||
import { Extension, Notifications } from "../helpers";
|
||||
|
||||
export class Cache {
|
||||
|
||||
public static async registerCommands() {
|
||||
const ext = Extension.getInstance();
|
||||
const subscriptions = ext.subscriptions;
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.clearCache, Cache.clear)
|
||||
);
|
||||
}
|
||||
|
||||
private static async clear() {
|
||||
const ext = Extension.getInstance();
|
||||
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, undefined, "workspace");
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Index, undefined, "workspace");
|
||||
|
||||
Notifications.info("Cache cleared");
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ export class Dashboard {
|
||||
});
|
||||
|
||||
SettingsHelper.onConfigChange(() => {
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
});
|
||||
|
||||
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
|
||||
|
||||
@@ -137,7 +137,7 @@ export class Folders {
|
||||
|
||||
Telemetry.send(TelemetryEvent.registerFolder);
|
||||
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ categories: []
|
||||
SettingsListener.setFramework(framework.name);
|
||||
}
|
||||
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
} catch (err: any) {
|
||||
Logger.error(`Project::init: ${err?.message || err}`);
|
||||
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
export * from './Article';
|
||||
export * from './Backers';
|
||||
export * from './Cache';
|
||||
export * from './Content';
|
||||
export * from './Dashboard';
|
||||
export * from './Diagnostics';
|
||||
export * from './Folders';
|
||||
export * from './Preview';
|
||||
export * from './Project';
|
||||
export * from './Settings';
|
||||
export * from './StatusListener';
|
||||
export * from './Template';
|
||||
export * from './Wysiwyg';
|
||||
|
||||
@@ -72,4 +72,7 @@ export const COMMAND_NAME = {
|
||||
|
||||
// Config
|
||||
reloadConfig: getCommandName("config.reload"),
|
||||
|
||||
// Cache
|
||||
clearCache: getCommandName("cache.clear"),
|
||||
};
|
||||
@@ -95,9 +95,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
onOpen={openFile} />
|
||||
</div>
|
||||
|
||||
<button onClick={openFile} className={`text-left`}><h2 className="mt-2 mb-2 font-bold">{escapedTitle}</h2></button>
|
||||
<button onClick={openFile} className={`text-left block`}><h2 className="mt-2 mb-2 font-bold">{escapedTitle}</h2></button>
|
||||
|
||||
<button onClick={openFile} className={`text-left`}><p className="text-xs text-vulcan-200 dark:text-whisper-800">{escapedDescription}</p></button>
|
||||
<button onClick={openFile} className={`text-left block`}><p className="text-xs text-vulcan-200 dark:text-whisper-800">{escapedDescription}</p></button>
|
||||
|
||||
{
|
||||
tags && tags.length > 0 && (
|
||||
|
||||
@@ -9,9 +9,9 @@ import { GroupOption } from '../../constants/GroupOption';
|
||||
import { Page } from '../../models/Page';
|
||||
import { Settings } from '../../models/Settings';
|
||||
import { GroupingSelector, PageAtom } from '../../state';
|
||||
import { PAGE_LIMIT } from '../Header/Pagination';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
|
||||
export interface IOverviewProps {
|
||||
pages: Page[];
|
||||
@@ -21,14 +21,15 @@ export interface IOverviewProps {
|
||||
export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settings}: React.PropsWithChildren<IOverviewProps>) => {
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const page = useRecoilValue(PageAtom);
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
|
||||
const pagedPages = useMemo(() => {
|
||||
if (settings?.dashboardState.contents.pagination) {
|
||||
return pages.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT));
|
||||
if (pageSetNr) {
|
||||
return pages.slice(page * pageSetNr, ((page + 1) * pageSetNr));
|
||||
}
|
||||
|
||||
return pages;
|
||||
}, [pages, page, settings]);
|
||||
}, [pages, page, pageSetNr]);
|
||||
|
||||
const groupName = useCallback((groupId, groupedPages) => {
|
||||
if (grouping === GroupOption.Draft) {
|
||||
|
||||
@@ -23,8 +23,10 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { SyncButton } from './SyncButton';
|
||||
import { PAGE_LIMIT, Pagination } from './Pagination';
|
||||
import { Pagination } from './Pagination';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { PaginationStatus } from './PaginationStatus';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -37,13 +39,14 @@ export interface IHeaderProps {
|
||||
folders?: string[];
|
||||
}
|
||||
|
||||
export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
|
||||
export const Header: React.FunctionComponent<IHeaderProps> = ({header, 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();
|
||||
const navigate = useNavigate();
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
|
||||
const createContent = () => {
|
||||
Messenger.send(DashboardMessage.createContent);
|
||||
@@ -180,8 +183,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPage
|
||||
</div>
|
||||
|
||||
{
|
||||
(settings?.dashboardState?.contents?.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && (
|
||||
<div className={`flex justify-center py-2 border-b border-gray-300 dark:border-vulcan-100`}>
|
||||
(pageSetNr > 0) && (totalPages || 0) > pageSetNr && (!grouping || grouping === GroupOption.none) && (
|
||||
<div className={`px-4 flex justify-between py-2 border-b border-gray-300 dark:border-vulcan-100`}>
|
||||
<PaginationStatus totalPages={totalPages || 0} />
|
||||
|
||||
<Pagination totalPages={totalPages || 0} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,43 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { routePaths } from '../..';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
|
||||
export interface IPaginationProps {
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export const PAGE_LIMIT = 16;
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = ({ totalPages }: React.PropsWithChildren<IPaginationProps>) => {
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const location = useLocation();
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const { pageSetNr, totalPagesNr } = usePagination(settings?.dashboardState.contents.pagination, totalPages, totalMedia);
|
||||
|
||||
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 getButtons = useCallback((): number[] => {
|
||||
const maxButtons = 5;
|
||||
const buttons: number[] = [];
|
||||
const start = page - maxButtons;
|
||||
const end = page + maxButtons;
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (i >= 0 && i <= totalItems) {
|
||||
if (i >= 0 && i <= totalPagesNr) {
|
||||
buttons.push(i);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
};
|
||||
}, [page, totalPagesNr]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(0);
|
||||
}, [pageSetNr]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(0);
|
||||
@@ -77,13 +71,13 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({ totalPag
|
||||
|
||||
<PaginationButton
|
||||
title="Next"
|
||||
disabled={page >= totalItems}
|
||||
disabled={page >= totalPagesNr}
|
||||
onClick={() => setPage(page + 1)} />
|
||||
|
||||
<PaginationButton
|
||||
title="Last"
|
||||
disabled={page >= totalItems}
|
||||
onClick={() => setPage(totalItems)} />
|
||||
disabled={page >= totalPagesNr}
|
||||
onClick={() => setPage(totalPagesNr)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import { PAGE_LIMIT } from './Pagination';
|
||||
import usePagination from '../../hooks/usePagination';
|
||||
import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state';
|
||||
|
||||
export interface IPaginationStatusProps {}
|
||||
export interface IPaginationStatusProps {
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> = (props: React.PropsWithChildren<IPaginationStatusProps>) => {
|
||||
export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> = ({ totalPages }: React.PropsWithChildren<IPaginationStatusProps>) => {
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const page = useRecoilValue(PageAtom);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const { pageSetNr, totalItems } = usePagination(settings?.dashboardState.contents.pagination, totalPages || 0, totalMedia);
|
||||
|
||||
const getTotalPage = () => {
|
||||
const mediaItems = ((page + 1) * PAGE_LIMIT);
|
||||
if (totalMedia < mediaItems) {
|
||||
return totalMedia;
|
||||
const totelItemsOnPage = useMemo(() => {
|
||||
const items = ((page + 1) * pageSetNr);
|
||||
if (totalItems < items) {
|
||||
return totalItems;
|
||||
}
|
||||
return mediaItems;
|
||||
};
|
||||
return totalItems;
|
||||
}, [page, totalMedia, pageSetNr]);
|
||||
|
||||
return (
|
||||
<div className="hidden sm:flex">
|
||||
<p className="text-sm text-gray-500 dark:text-whisper-900">
|
||||
Showing <span className="font-medium">{(page * PAGE_LIMIT) + 1}</span> to <span className="font-medium">{getTotalPage()}</span> of{' '}
|
||||
<span className="font-medium">{totalMedia}</span> results
|
||||
Showing <span className="font-medium">{(page * pageSetNr) + 1}</span> to <span className="font-medium">{totelItemsOnPage}</span> of{' '}
|
||||
<span className="font-medium">{totalItems}</span> results
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,9 +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, SelectedMediaFolderAtom } from '../state';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../state';
|
||||
import Fuse from 'fuse.js';
|
||||
import { PAGE_LIMIT } from '../components/Header/Pagination';
|
||||
import usePagination from './usePagination';
|
||||
|
||||
const fuseOptions: Fuse.IFuseOptions<MediaInfo> = {
|
||||
keys: [
|
||||
@@ -28,10 +28,12 @@ export default function useMedia() {
|
||||
const [ , setFolders ] = useRecoilState(MediaFoldersAtom);
|
||||
const [ , setLoading ] = useRecoilState(LoadingAtom);
|
||||
const search = useRecoilValue(SearchAtom);
|
||||
const settings = useRecoilValue(SettingsAtom);
|
||||
const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination);
|
||||
|
||||
const getMedia = useCallback(() => {
|
||||
return searchedMedia.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT));
|
||||
}, [searchedMedia, page]);
|
||||
return searchedMedia.slice(page * pageSetNr, ((page + 1) * pageSetNr));
|
||||
}, [searchedMedia, page, pageSetNr]);
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<MediaPaths | { key: string, value: any }>>) => {
|
||||
if (message.data.command === DashboardCommand.media) {
|
||||
@@ -57,8 +59,9 @@ export default function useMedia() {
|
||||
return;
|
||||
}
|
||||
|
||||
setTotal(media.length);
|
||||
setSearchedMedia(media);
|
||||
}, [search]);
|
||||
}, [search, media]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen<MediaPaths>(messageListener);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { parseWinPath } from '../../helpers/parseWinPath';
|
||||
|
||||
export default function usePages(pages: Page[]) {
|
||||
const [ pageItems, setPageItems ] = useState<Page[]>([]);
|
||||
const [ sortedPages, setSortedPages ] = useState<Page[]>([]);
|
||||
const [ sorting, setSorting ] = useRecoilState(SortingAtom);
|
||||
const [ tabInfo , setTabInfo ] = useRecoilState(TabInfoAtom);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
@@ -22,8 +23,10 @@ export default function usePages(pages: Page[]) {
|
||||
const tag = useRecoilValue(TagSelector);
|
||||
const category = useRecoilValue(CategorySelector);
|
||||
|
||||
const processPages = useCallback((searchedPages: Page[]) => {
|
||||
const draftField = settings?.draftField;
|
||||
/**
|
||||
* Process all the pages by applying the sorting, filtering and searching.
|
||||
*/
|
||||
const processPages = useCallback((searchedPages: Page[], fullProcess: boolean = true) => {
|
||||
const framework = settings?.crntFramework;
|
||||
|
||||
// Filter the pages
|
||||
@@ -93,40 +96,52 @@ export default function usePages(pages: Page[]) {
|
||||
pagesSorted = pagesSorted.filter(page => page.fmCategories && page.fmCategories.includes(category));
|
||||
}
|
||||
|
||||
setSortedPages(pagesSorted);
|
||||
}, [ settings, tab, folder, search, tag, category, sorting, tabInfo ]);
|
||||
|
||||
|
||||
/**
|
||||
* Process the pages when the tab changes
|
||||
*/
|
||||
const processByTab = useCallback((pages: Page[]) => {
|
||||
const draftField = settings?.draftField;
|
||||
|
||||
let crntPages: Page[] = Object.assign([], pages);
|
||||
|
||||
// Process the tab data
|
||||
const draftTypes = Object.assign({}, tabInfo);
|
||||
draftTypes[Tab.All] = pagesSorted.length;
|
||||
draftTypes[Tab.All] = crntPages.length;
|
||||
|
||||
// Filter by draft status
|
||||
if (draftField && draftField.type === 'choice') {
|
||||
const draftChoices = settings?.draftField?.choices;
|
||||
for (const choice of (draftChoices || [])) {
|
||||
if (choice) {
|
||||
draftTypes[choice] = pagesSorted.filter(page => page.fmDraft === choice).length;
|
||||
draftTypes[choice] = crntPages.filter(page => page.fmDraft === choice).length;
|
||||
}
|
||||
}
|
||||
|
||||
if (tab !== Tab.All) {
|
||||
pagesSorted = pagesSorted.filter(page => page.fmDraft === tab);
|
||||
crntPages = crntPages.filter(page => page.fmDraft === tab);
|
||||
} else {
|
||||
pagesSorted = pagesSorted;
|
||||
crntPages = crntPages;
|
||||
}
|
||||
} else {
|
||||
// Draft field is a boolean field
|
||||
const draftFieldName = draftField?.name || "draft";
|
||||
|
||||
const drafts = pagesSorted.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true");
|
||||
const published = pagesSorted.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined");
|
||||
const drafts = crntPages.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true");
|
||||
const published = crntPages.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined");
|
||||
|
||||
draftTypes[Tab.Draft] = draftField?.invert ? published.length : drafts.length;
|
||||
draftTypes[Tab.Published] = draftField?.invert ? drafts.length : published.length;
|
||||
|
||||
if (tab === Tab.Published) {
|
||||
pagesSorted = draftField?.invert ? drafts : published;
|
||||
crntPages = draftField?.invert ? drafts : published;
|
||||
} else if (tab === Tab.Draft) {
|
||||
pagesSorted = draftField?.invert ? published : drafts;
|
||||
crntPages = draftField?.invert ? published : drafts;
|
||||
} else {
|
||||
pagesSorted = pagesSorted;
|
||||
crntPages = crntPages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,10 +149,14 @@ export default function usePages(pages: Page[]) {
|
||||
setTabInfo(draftTypes);
|
||||
|
||||
// Set the pages
|
||||
setPageItems(pagesSorted);
|
||||
}, [ settings, tab, folder, search, tag, category, sorting, tabInfo ]);
|
||||
|
||||
setPageItems(crntPages);
|
||||
}, [ tab, tabInfo, settings ]);
|
||||
|
||||
|
||||
/**
|
||||
* Search listener for filtered pages
|
||||
* @param message
|
||||
*/
|
||||
const searchListener = (message: MessageEvent<EventData<any>>) => {
|
||||
switch (message.data.command) {
|
||||
case DashboardMessage.searchPages:
|
||||
@@ -146,6 +165,7 @@ export default function usePages(pages: Page[]) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let usedSorting = sorting;
|
||||
|
||||
@@ -160,15 +180,19 @@ export default function usePages(pages: Page[]) {
|
||||
// Check if search needs to be performed
|
||||
let searchedPages = pages;
|
||||
if (search) {
|
||||
// const fuse = new Fuse(pages, fuseOptions);
|
||||
// const results = fuse.search(search);
|
||||
// searchedPages = results.map(page => page.item);
|
||||
|
||||
Messenger.send(DashboardMessage.searchPages, { query: search });
|
||||
} else {
|
||||
processPages(searchedPages);
|
||||
}
|
||||
}, [ settings?.draftField, pages, sorting, search, tab, tag, category, folder ]);
|
||||
}, [ settings?.draftField, pages, sorting, search, tag, category, folder ]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (sortedPages.length > 0) {
|
||||
processByTab(sortedPages);
|
||||
}
|
||||
}, [sortedPages, tab])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(searchListener);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { routePaths } from '..';
|
||||
|
||||
export const PAGE_LIMIT = 16;
|
||||
|
||||
export default function usePagination(value: number | boolean | null | undefined, totalPages?: number, totalMedia?: number) {
|
||||
const location = useLocation();
|
||||
|
||||
const pagination = useMemo(() => {
|
||||
if (location.pathname === routePaths.contents) {
|
||||
if (typeof value === 'number') {
|
||||
const pageNr = value > 0 ? value : 0;
|
||||
if (pageNr > 52) {
|
||||
return 52;
|
||||
}
|
||||
return pageNr;
|
||||
} else if (typeof value === 'boolean') {
|
||||
return value ? PAGE_LIMIT : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return PAGE_LIMIT;
|
||||
}, [value, location.pathname]);
|
||||
|
||||
|
||||
const totalPagesNr: number = useMemo(() => {
|
||||
if (location.pathname === routePaths.contents) {
|
||||
if (totalPages) {
|
||||
return Math.ceil((totalPages || 0) / pagination) - 1
|
||||
}
|
||||
} else {
|
||||
if (totalMedia) {
|
||||
return Math.ceil(totalMedia / pagination) - 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}, [location.pathname, totalPages, totalMedia, pagination]);
|
||||
|
||||
/**
|
||||
* The total items (pages or media)
|
||||
*/
|
||||
const totalItems: number = useMemo(() => {
|
||||
if (location.pathname === routePaths.contents) {
|
||||
if (totalPages) {
|
||||
return totalPages;
|
||||
}
|
||||
} else {
|
||||
if (totalMedia) {
|
||||
return totalMedia;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}, [location.pathname, totalPages, totalMedia, pagination]);
|
||||
|
||||
|
||||
return {
|
||||
pageSetNr: pagination,
|
||||
totalPagesNr,
|
||||
totalItems
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Uri } from "vscode";
|
||||
|
||||
export interface Page {
|
||||
// Properties for caching
|
||||
fmCachePath: string;
|
||||
fmCacheModifiedTime: number;
|
||||
|
||||
// Front matter fields
|
||||
fmFolder: string;
|
||||
fmFilePath: string;
|
||||
fmFileName: string;
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface ContentsViewState {
|
||||
defaultSorting: string | null | undefined;
|
||||
tags: string | null | undefined;
|
||||
templatesEnabled: boolean | null | undefined;
|
||||
pagination: boolean | null | undefined;
|
||||
pagination: boolean | number | null | undefined;
|
||||
}
|
||||
|
||||
export interface MediaViewState extends ContentsViewState {
|
||||
|
||||
+10
-1
@@ -8,6 +8,7 @@ import { Folders } from './commands/Folders';
|
||||
import { Preview } from './commands/Preview';
|
||||
import { Project } from './commands/Project';
|
||||
import { Template } from './commands/Template';
|
||||
import { Cache } from './commands/Cache';
|
||||
import { COMMAND_NAME, TelemetryEvent } from './constants';
|
||||
import { TaxonomyType } from './models';
|
||||
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
|
||||
@@ -15,7 +16,7 @@ import { TagType } from './panelWebView/TagType';
|
||||
import { ExplorerView } from './explorerView/ExplorerView';
|
||||
import { Extension } from './helpers/Extension';
|
||||
import { DashboardData } from './models/DashboardData';
|
||||
import { debounceCallback, Logger, Settings as SettingsHelper } from './helpers';
|
||||
import { DashboardSettings, debounceCallback, Logger, Settings as SettingsHelper } from './helpers';
|
||||
import { Content } from './commands/Content';
|
||||
import ContentProvider from './providers/ContentProvider';
|
||||
import { Wysiwyg } from './commands/Wysiwyg';
|
||||
@@ -25,6 +26,7 @@ import { Backers } from './commands/Backers';
|
||||
import { DataListener, SettingsListener } from './listeners/panel';
|
||||
import { NavigationType } from './dashboardWebView/models';
|
||||
import { ModeSwitch } from './services/ModeSwitch';
|
||||
import { PagesParser } from './services/PagesParser';
|
||||
|
||||
let frontMatterStatusBar: vscode.StatusBarItem;
|
||||
let statusDebouncer: { (fnc: any, time: number): void; };
|
||||
@@ -266,6 +268,13 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Git
|
||||
GitListener.init();
|
||||
|
||||
// Once everything is registered, the page parsing can start in the background
|
||||
DashboardSettings.get();
|
||||
PagesParser.start();
|
||||
|
||||
// Cache commands
|
||||
Cache.registerCommands();
|
||||
|
||||
// Subscribe all commands
|
||||
subscriptions.push(
|
||||
insertTags,
|
||||
|
||||
@@ -16,15 +16,24 @@ import { parseWinPath } from './parseWinPath';
|
||||
|
||||
|
||||
export class DashboardSettings {
|
||||
private static cachedSettings: ISettings | undefined = undefined;
|
||||
|
||||
public static async get() {
|
||||
public static async get(clear: boolean = false) {
|
||||
if (!this.cachedSettings || clear) {
|
||||
this.cachedSettings = await this.getSettings();
|
||||
}
|
||||
|
||||
return this.cachedSettings;
|
||||
}
|
||||
|
||||
public static async getSettings() {
|
||||
const ext = Extension.getInstance();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = Project.isInitialized();
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
const pagination = Settings.get<boolean>(SETTING_DASHBOARD_CONTENT_PAGINATION)
|
||||
const pagination = Settings.get<boolean | number>(SETTING_DASHBOARD_CONTENT_PAGINATION)
|
||||
|
||||
return {
|
||||
const settings = {
|
||||
git: {
|
||||
isGitRepo: gitActions ? await GitListener.isGitRepository() : false,
|
||||
actions: gitActions || false
|
||||
@@ -71,7 +80,9 @@ export class DashboardSettings {
|
||||
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
|
||||
snippets: Settings.get<Snippets>(SETTING_CONTENT_SNIPPETS),
|
||||
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global')
|
||||
} as ISettings
|
||||
} as ISettings;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from './../../constants/ContentType';
|
||||
import { isValidFile } from '../../helpers/isValidFile';
|
||||
import { existsSync, unlinkSync } from "fs";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { unlinkSync } from "fs";
|
||||
import { basename } from "path";
|
||||
import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from "vscode";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { Folders } from "../../commands/Folders";
|
||||
import { COMMAND_NAME, DefaultFields, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants";
|
||||
import { COMMAND_NAME, ExtensionState } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Page } from "../../dashboardWebView/models";
|
||||
import { ArticleHelper, Extension, Logger, Settings } from "../../helpers";
|
||||
import { ContentType } from "../../helpers/ContentType";
|
||||
import { DateHelper } from "../../helpers/DateHelper";
|
||||
import { Notifications } from "../../helpers/Notifications";
|
||||
import { ArticleHelper, Extension, Logger } from "../../helpers";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { DataListener } from '../panel';
|
||||
import Fuse from 'fuse.js';
|
||||
import { PagesParser } from '../../services/PagesParser';
|
||||
|
||||
|
||||
export class PagesListener extends BaseListener {
|
||||
@@ -132,7 +128,7 @@ export class PagesListener extends BaseListener {
|
||||
if (pageIdx !== -1) {
|
||||
const stats = await workspace.fs.stat(file);
|
||||
const crntPage = this.lastPages[pageIdx];
|
||||
const updatedPage = this.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder);
|
||||
const updatedPage = PagesParser.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder);
|
||||
if (updatedPage) {
|
||||
this.lastPages[pageIdx] = updatedPage;
|
||||
this.sendPageData(this.lastPages);
|
||||
@@ -156,43 +152,18 @@ export class PagesListener extends BaseListener {
|
||||
if (cachedPages) {
|
||||
this.sendPageData(cachedPages);
|
||||
}
|
||||
} else {
|
||||
PagesParser.reset();
|
||||
}
|
||||
|
||||
// Update the dashboard with the fresh data
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
PagesParser.getPages(async (pages: Page[]) => {
|
||||
this.lastPages = pages;
|
||||
this.sendPageData(pages);
|
||||
|
||||
if (folderInfo) {
|
||||
for (const folder of folderInfo) {
|
||||
for (const file of folder.lastModified) {
|
||||
if (isValidFile(file.fileName)) {
|
||||
try {
|
||||
const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title);
|
||||
|
||||
if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) {
|
||||
pages.push(page);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
if ((error as Error)?.message.toLowerCase() === "webview is disposed") {
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.error(`PagesListener::getPagesData: ${file.filePath} - ${error.message}`);
|
||||
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lastPages = pages;
|
||||
this.sendPageData(pages);
|
||||
|
||||
this.sendMsg(DashboardCommand.searchReady, true);
|
||||
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace");
|
||||
await this.createSearchIndex(pages);
|
||||
this.sendMsg(DashboardCommand.searchReady, true);
|
||||
|
||||
await this.createSearchIndex(pages);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,136 +216,4 @@ export class PagesListener extends BaseListener {
|
||||
public static refresh() {
|
||||
this.getPagesData(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the page content
|
||||
* @param filePath
|
||||
* @param fileMtime
|
||||
* @param fileName
|
||||
* @param folderTitle
|
||||
* @returns
|
||||
*/
|
||||
private static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined {
|
||||
const article = ArticleHelper.getFrontMatterByPath(filePath);
|
||||
|
||||
if (article?.data.title) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
|
||||
|
||||
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
|
||||
const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined;
|
||||
|
||||
const modifiedField = ArticleHelper.getModifiedDateField(article) || null;
|
||||
const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined;
|
||||
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
|
||||
const page: Page = {
|
||||
...article.data,
|
||||
// FrontMatter properties
|
||||
fmFolder: folderTitle,
|
||||
fmFilePath: filePath,
|
||||
fmFileName: fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
|
||||
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
|
||||
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
|
||||
fmPreviewImage: "",
|
||||
fmTags: [],
|
||||
fmCategories: [],
|
||||
fmContentType: DEFAULT_CONTENT_TYPE_NAME,
|
||||
fmBody: article?.content || "",
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
date: article?.data[dateField] || "",
|
||||
draft: article?.data.draft,
|
||||
description: article?.data[descriptionField] || "",
|
||||
};
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
if (contentType) {
|
||||
page.fmContentType = contentType.name;
|
||||
}
|
||||
|
||||
let previewFieldParents = ContentType.findPreviewField(contentType.fields);
|
||||
if (previewFieldParents.length === 0) {
|
||||
const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview");
|
||||
if (previewField) {
|
||||
previewFieldParents = ["preview"];
|
||||
}
|
||||
}
|
||||
|
||||
let tagParents = ContentType.findFieldByType(contentType.fields, "tags");
|
||||
const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]);
|
||||
page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue;
|
||||
|
||||
let categoryParents = ContentType.findFieldByType(contentType.fields, "categories");
|
||||
const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]);
|
||||
page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue;
|
||||
|
||||
// Check if parent fields were retrieved, if not there was no image present
|
||||
if (previewFieldParents.length > 0) {
|
||||
let fieldValue = null;
|
||||
let crntPageData = article?.data;
|
||||
|
||||
for (let i = 0; i < previewFieldParents.length; i++) {
|
||||
const previewField = previewFieldParents[i];
|
||||
|
||||
if (i === previewFieldParents.length - 1) {
|
||||
fieldValue = crntPageData[previewField];
|
||||
} else {
|
||||
if (!crntPageData[previewField]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
crntPageData = crntPageData[previewField];
|
||||
|
||||
// Check for preview image in block data
|
||||
if (crntPageData instanceof Array && crntPageData.length > 0) {
|
||||
// Get the first field block that contains the next field data
|
||||
const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]);
|
||||
if (fieldData) {
|
||||
crntPageData = fieldData;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldValue && wsFolder) {
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.getWebview()?.asWebviewUri(previewUri);
|
||||
page["fmPreviewImage"] = preview?.toString() || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,15 @@ export class SettingsListener extends BaseListener {
|
||||
private static async update(data: { name: string, value: any }) {
|
||||
if (data.name) {
|
||||
await Settings.update(data.name, data.value);
|
||||
this.getSettings();
|
||||
this.getSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings for the dashboard
|
||||
*/
|
||||
public static async getSettings() {
|
||||
const settings = await DashboardSettings.get();
|
||||
public static async getSettings(clear: boolean = false) {
|
||||
const settings = await DashboardSettings.get(clear);
|
||||
|
||||
this.sendMsg(DashboardCommand.settings, settings);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export class SettingsListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
}
|
||||
|
||||
private static addFolder(folder: string) {
|
||||
|
||||
@@ -57,7 +57,7 @@ export class SnippetListener extends BaseListener {
|
||||
snippets[title] = snippetContent;
|
||||
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
}
|
||||
|
||||
private static async updateSnippet(data: any) {
|
||||
@@ -69,7 +69,7 @@ export class SnippetListener extends BaseListener {
|
||||
}
|
||||
|
||||
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
|
||||
SettingsListener.getSettings();
|
||||
SettingsListener.getSettings(true);
|
||||
}
|
||||
|
||||
private static async insertSnippet(data: any) {
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
import { parseWinPath } from './../helpers/parseWinPath';
|
||||
import { existsSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { StatusBarAlignment, Uri, window } from "vscode";
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../constants";
|
||||
import { Page } from "../dashboardWebView/models";
|
||||
import { ArticleHelper, ContentType, DateHelper, Extension, isValidFile, Logger, Notifications, Settings } from "../helpers";
|
||||
|
||||
|
||||
export class PagesParser {
|
||||
public static allPages: Page[] = [];
|
||||
public static cachedPages: Page[] | undefined = undefined;
|
||||
private static parser: Promise<void> | undefined;
|
||||
private static initialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Start the page parser
|
||||
*/
|
||||
public static start() {
|
||||
if (!this.parser) {
|
||||
this.parser = this.parsePages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the pages
|
||||
* @param cb
|
||||
*/
|
||||
public static getPages(cb: (pages: Page[]) => void) {
|
||||
if (this.parser) {
|
||||
this.parser.then(() => cb(PagesParser.allPages));
|
||||
} else if (!PagesParser.initialized) {
|
||||
this.parser = this.parsePages();
|
||||
this.parser.then(() => cb(PagesParser.allPages));
|
||||
} else if (PagesParser.allPages === undefined || PagesParser.allPages.length === 0) {
|
||||
this.parser = this.parsePages();
|
||||
this.parser.then(() => cb(PagesParser.allPages));
|
||||
} else {
|
||||
cb(PagesParser.allPages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cache
|
||||
*/
|
||||
public static async reset() {
|
||||
this.parser = undefined;
|
||||
PagesParser.allPages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all pages in the workspace
|
||||
*/
|
||||
public static async parsePages() {
|
||||
const ext = Extension.getInstance();
|
||||
|
||||
// Update the dashboard with the fresh data
|
||||
const folderInfo = await Folders.getInfo();
|
||||
const pages: Page[] = [];
|
||||
const statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
|
||||
|
||||
if (folderInfo) {
|
||||
statusBar.text = '$(sync~spin) Processing pages...';
|
||||
statusBar.show();
|
||||
|
||||
for (const folder of folderInfo) {
|
||||
for (const file of folder.lastModified) {
|
||||
if (isValidFile(file.fileName)) {
|
||||
try {
|
||||
let page = await PagesParser.getCachedPage(file.filePath, file.mtime);
|
||||
|
||||
if (!page) {
|
||||
page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title);
|
||||
}
|
||||
|
||||
if (page && !pages.find(p => p.fmFilePath === page?.fmFilePath)) {
|
||||
pages.push(page);
|
||||
}
|
||||
} catch (error: any) {
|
||||
if ((error as Error)?.message.toLowerCase() === "webview is disposed") {
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.error(`PagesParser::parsePages: ${file.filePath} - ${error.message}`);
|
||||
Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace");
|
||||
PagesParser.cachedPages = undefined;
|
||||
|
||||
this.parser = undefined;
|
||||
this.initialized = true;
|
||||
PagesParser.allPages = [...pages];
|
||||
statusBar.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the page in the cached data
|
||||
* @param filePath
|
||||
* @param modifiedTime
|
||||
* @returns
|
||||
*/
|
||||
public static async getCachedPage(filePath: string, modifiedTime: number): Promise<Page | undefined> {
|
||||
if (!PagesParser.cachedPages) {
|
||||
const ext = Extension.getInstance();
|
||||
PagesParser.cachedPages = await ext.getState<Page[]>(ExtensionState.Dashboard.Pages.Cache, "workspace") || [];
|
||||
}
|
||||
|
||||
return PagesParser.cachedPages.find(p => p.fmCachePath === parseWinPath(filePath) && p.fmCacheModifiedTime === modifiedTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the page content
|
||||
* @param filePath
|
||||
* @param fileMtime
|
||||
* @param fileName
|
||||
* @param folderTitle
|
||||
* @returns
|
||||
*/
|
||||
public static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined {
|
||||
const article = ArticleHelper.getFrontMatterByPath(filePath);
|
||||
|
||||
if (article?.data.title) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
|
||||
|
||||
const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate;
|
||||
const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined;
|
||||
|
||||
const modifiedField = ArticleHelper.getModifiedDateField(article) || null;
|
||||
const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined;
|
||||
|
||||
const staticFolder = Folders.getStaticFolderRelativePath();
|
||||
|
||||
let escapedTitle = article?.data.title;
|
||||
if (escapedTitle && typeof escapedTitle !== "string") {
|
||||
escapedTitle = "<invalid title>";
|
||||
}
|
||||
|
||||
let escapedDescription = article?.data[descriptionField] || "";
|
||||
if (escapedDescription && typeof escapedDescription !== "string") {
|
||||
escapedDescription = "<invalid title>";
|
||||
}
|
||||
|
||||
const page: Page = {
|
||||
...article.data,
|
||||
// Cache properties
|
||||
fmCachePath: parseWinPath(filePath),
|
||||
fmCacheModifiedTime: fileMtime,
|
||||
// FrontMatter properties
|
||||
fmFolder: folderTitle,
|
||||
fmFilePath: filePath,
|
||||
fmFileName: fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime,
|
||||
fmPublished: dateFieldValue ? dateFieldValue.getTime() : null,
|
||||
fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null,
|
||||
fmPreviewImage: "",
|
||||
fmTags: [],
|
||||
fmCategories: [],
|
||||
fmContentType: DEFAULT_CONTENT_TYPE_NAME,
|
||||
fmBody: article?.content || "",
|
||||
// Make sure these are always set
|
||||
title: escapedTitle,
|
||||
slug: article?.data.slug,
|
||||
date: article?.data[dateField] || "",
|
||||
draft: article?.data.draft,
|
||||
description: escapedDescription,
|
||||
};
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
if (contentType) {
|
||||
page.fmContentType = contentType.name;
|
||||
}
|
||||
|
||||
let previewFieldParents = ContentType.findPreviewField(contentType.fields);
|
||||
if (previewFieldParents.length === 0) {
|
||||
const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview");
|
||||
if (previewField) {
|
||||
previewFieldParents = ["preview"];
|
||||
}
|
||||
}
|
||||
|
||||
let tagParents = ContentType.findFieldByType(contentType.fields, "tags");
|
||||
const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]);
|
||||
page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue;
|
||||
|
||||
let categoryParents = ContentType.findFieldByType(contentType.fields, "categories");
|
||||
const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]);
|
||||
page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue;
|
||||
|
||||
// Check if parent fields were retrieved, if not there was no image present
|
||||
if (previewFieldParents.length > 0) {
|
||||
let fieldValue = null;
|
||||
let crntPageData = article?.data;
|
||||
|
||||
for (let i = 0; i < previewFieldParents.length; i++) {
|
||||
const previewField = previewFieldParents[i];
|
||||
|
||||
if (i === previewFieldParents.length - 1) {
|
||||
fieldValue = crntPageData[previewField];
|
||||
} else {
|
||||
if (!crntPageData[previewField]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
crntPageData = crntPageData[previewField];
|
||||
|
||||
// Check for preview image in block data
|
||||
if (crntPageData instanceof Array && crntPageData.length > 0) {
|
||||
// Get the first field block that contains the next field data
|
||||
const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]);
|
||||
if (fieldData) {
|
||||
crntPageData = fieldData;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldValue && wsFolder) {
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
let previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri);
|
||||
|
||||
if (!previewPath) {
|
||||
previewPath = PagesParser.getWebviewUri(previewUri);
|
||||
}
|
||||
|
||||
page["fmPreviewImage"] = previewPath?.toString() || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the webview URI
|
||||
* @param resource
|
||||
* @returns
|
||||
*/
|
||||
private static getWebviewUri(resource: Uri) {
|
||||
// Logic from: https://github.com/microsoft/vscode/blob/main/src/vs/workbench/common/webview.ts
|
||||
const webviewResourceBaseHost = 'vscode-cdn.net';
|
||||
const webviewRootResourceAuthority = `vscode-resource.${webviewResourceBaseHost}`;
|
||||
|
||||
const authority = `${resource.scheme}+${encodeURI(resource.authority)}.${webviewRootResourceAuthority}`;
|
||||
return Uri.from({
|
||||
scheme: "https",
|
||||
authority,
|
||||
path: resource.path,
|
||||
query: resource.query,
|
||||
fragment: resource.fragment
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user