Compare commits

...

22 Commits

Author SHA1 Message Date
Elio Struyf
f5b636d960 Enhance Copilot extension detection and update model family to gpt-4.1 2025-12-01 09:38:49 +01:00
Elio Struyf
7fac27b73e Enhancement: Dashboard "Structure" for Docs
Fixes #937
2025-09-09 19:52:33 +02:00
Elio Struyf
aa0ee4708a Merge branch 'copilot/fix-937' into beta 2025-09-09 19:51:43 +02:00
Elio Struyf
24c26ac855 Refactor StructureView and Overview components for improved readability; remove unused sorting logic and adjust layout styles 2025-09-09 19:50:21 +02:00
Elio Struyf
cda217ac76 Enhance StructureView to normalize folder paths and improve page assignment logic 2025-09-09 19:14:38 +02:00
Elio Struyf
cb42bd4b4b Merge branches 'copilot/fix-937' and 'copilot/fix-937' of github.com:estruyf/vscode-front-matter into copilot/fix-937 2025-09-09 16:11:44 +02:00
copilot-swe-agent[bot]
d4c5ca1c18 Fix folder path normalization in Structure view for proper nesting
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-09-09 13:56:45 +00:00
Elio Struyf
61398c4e25 Merge branch 'copilot/fix-937' of github.com:estruyf/vscode-front-matter into copilot/fix-937 2025-09-09 15:47:24 +02:00
copilot-swe-agent[bot]
b62d1e8177 Fix folder hierarchy rendering in Structure view
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-09-09 13:32:16 +00:00
copilot-swe-agent[bot]
65fc9f38ed Fix Item rendering for Structure view type
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-09-09 13:17:44 +00:00
Elio Struyf
c8ebac32d3 Update CHANGELOG for version 10.10.0: Add SEO keyword support enhancement 2025-09-09 09:42:13 +02:00
Elio Struyf
219c4bd657 Merge branch 'copilot/fix-965' into beta 2025-09-09 09:39:45 +02:00
copilot-swe-agent[bot]
73e58c7b52 Add multi-language localization support for Structure view
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-09-08 20:16:45 +00:00
copilot-swe-agent[bot]
e4147eed09 Implement Structure view for dashboard with folder hierarchy display
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
2025-09-08 20:11:43 +00:00
copilot-swe-agent[bot]
f3df0f6856 Initial plan 2025-09-08 19:53:11 +00:00
Elio Struyf
bb535961a3 Update CHANGELOG for version 10.10.0: Add enhancements and fixes sections 2025-09-08 21:49:21 +02:00
Elio Struyf
0c7e3fb42b Enhancement: Support for numbers (int) in Snippets
Fixes #973
2025-09-08 21:48:30 +02:00
Elio Struyf
a6188b0060 Add entry for version 10.10.0 to CHANGELOG with typo fix on welcome screen 2025-07-15 23:27:28 +02:00
Elio Struyf
43a6a22721 Fix typo #969 2025-07-15 23:26:45 +02:00
Elio Struyf
99405042ed Refactor date change handling in DateTimeField to ensure proper timezone formatting and fallback to ISO string 2025-07-14 21:24:31 +02:00
Elio Struyf
76b103cb62 Merge branch 'beta' of github.com:estruyf/vscode-front-matter into beta 2025-07-03 20:17:55 +02:00
Elio Struyf
be158d4365 Update docs 2025-07-03 20:17:44 +02:00
23 changed files with 464 additions and 38 deletions

View File

@@ -1,5 +1,17 @@
# Change Log
## [10.10.0] - 2025-xx-xx
### 🎨 Enhancements
- [#937](https://github.com/estruyf/vscode-front-matter/issues/937): Dashboard "Structure" view for documentation sites
- [#965](https://github.com/estruyf/vscode-front-matter/issues/965): Added SEO support for the keyword in the first paragraph
- [#973](https://github.com/estruyf/vscode-front-matter/issues/973): Support for number fields in the snippets
### 🐞 Fixes
- [#969](https://github.com/estruyf/vscode-front-matter/issues/969): Fix typo on welcome screen
## [10.9.0] - 2025-07-01 - [Release notes](https://beta.frontmatter.codes/updates/v10.9.0)
### 🎨 Enhancements

View File

@@ -117,7 +117,7 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
You can get the extension via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
@@ -129,7 +129,7 @@ If you have the courage to test out the beta features, we made available a beta
- Uninstall the main Front Matter version
- Install the beta version
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).

View File

@@ -115,7 +115,7 @@ In version v2 we released the re-designed sidebar panel with improved SEO suppor
You can get the extension via:
- The VS Code marketplace: [VS Code Marketplace - Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter).
- The extension CLI: `ext install eliostruyf.vscode-front-matter`
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter">open extension in VS Code</a>
> **Info**: The docs can be found on [frontmatter.codes](https://frontmatter.codes).
@@ -127,7 +127,7 @@ If you have the courage to test out the beta features, we made available a beta
- Uninstall the main Front Matter version
- Install the beta version
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- The extension CLI: `code --install-extension eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
> **Info**: The BETA docs can be found on [beta.frontmatter.codes](https://beta.frontmatter.codes).

View File

@@ -109,6 +109,7 @@
"dashboard.header.tabs.taxonomies": "Taxonomien",
"dashboard.header.viewSwitch.toGrid": "Zur Rasteransicht wechseln",
"dashboard.header.viewSwitch.toList": "Zur Listenansicht wechseln",
"dashboard.header.viewSwitch.toStructure": "Zur Strukturansicht wechseln",
"dashboard.layout.sponsor.support.msg": "Unterstützen Sie Front Matter",
"dashboard.layout.sponsor.review.label": "Bewerten",
"dashboard.layout.sponsor.review.msg": "Bewerten Sie Front Matter",

View File

@@ -109,6 +109,7 @@
"dashboard.header.tabs.taxonomies": "Taxonomies",
"dashboard.header.viewSwitch.toGrid": "Afficher en grille",
"dashboard.header.viewSwitch.toList": "Afficher en liste",
"dashboard.header.viewSwitch.toStructure": "Afficher en structure",
"dashboard.layout.sponsor.support.msg": "Soutenir Front Matter",
"dashboard.layout.sponsor.review.label": "Donnez votre avis",
"dashboard.layout.sponsor.review.msg": "Donnez votre avis sur Front Matter",

View File

@@ -214,6 +214,7 @@
"dashboard.header.viewSwitch.toGrid": "グリッド表示",
"dashboard.header.viewSwitch.toList": "リスト表示",
"dashboard.header.viewSwitch.toStructure": "構造表示",
"dashboard.layout.sponsor.support.msg": "Front Matterをサポートする",
"dashboard.layout.sponsor.review.label": "評価する",

View File

@@ -222,6 +222,7 @@
"dashboard.header.viewSwitch.toGrid": "Change to grid",
"dashboard.header.viewSwitch.toList": "Change to list",
"dashboard.header.viewSwitch.toStructure": "Change to structure",
"dashboard.layout.sponsor.support.msg": "Support Front Matter",
"dashboard.layout.sponsor.review.label": "Review",
@@ -332,7 +333,7 @@
"dashboard.steps.stepsToGetStarted.tags.name": "Import all tags and categories (optional)",
"dashboard.steps.stepsToGetStarted.tags.description": "Now that Front Matter knows all the content folders. Would you like to import all tags and categories from the available content?",
"dashboard.steps.stepsToGetStarted.git.name": "Do you want to enable Git synchronization?",
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to eaily sync your changes with your repository.",
"dashboard.steps.stepsToGetStarted.git.description": "Enable Git synchronization to easily sync your changes with your repository.",
"dashboard.steps.stepsToGetStarted.showDashboard.name": "Show the dashboard",
"dashboard.steps.stepsToGetStarted.showDashboard.description": "Once all actions are completed, the dashboard can be loaded.",
"dashboard.steps.stepsToGetStarted.template.name": "Use a configuration template",

View File

@@ -222,6 +222,7 @@
"dashboard.header.viewSwitch.toGrid": "切换到网格视图",
"dashboard.header.viewSwitch.toList": "切换到列表视图",
"dashboard.header.viewSwitch.toStructure": "切换到结构视图",
"dashboard.layout.sponsor.support.msg": "支持 Front Matter",
"dashboard.layout.sponsor.review.label": "评价",

View File

@@ -0,0 +1,68 @@
import { XCircleIcon } from '@heroicons/react/24/solid';
import * as React from 'react';
export interface INumberFieldProps {
name: string;
value?: string;
placeholder?: string;
description?: string;
icon?: JSX.Element;
disabled?: boolean;
autoFocus?: boolean;
onChange?: (value: string) => void;
onReset?: () => void;
}
export const NumberField: React.FunctionComponent<INumberFieldProps> = ({
name,
value,
placeholder,
description,
icon,
autoFocus,
disabled,
onChange,
onReset
}: React.PropsWithChildren<INumberFieldProps>) => {
return (
<>
<div className="relative flex justify-center">
{
icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
{icon}
</div>
)
}
<input
type="number"
name={name}
className={`block w-full py-2 ${icon ? "pl-10" : "pl-2"} pr-2 sm:text-sm appearance-none disabled:opacity-50 rounded bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] placeholder-[var(--vscode-input-placeholderForeground)] border-[var(--frontmatter-border)] focus:border-[var(--vscode-focusBorder)] focus:outline-0`}
style={{
boxShadow: "none"
}}
placeholder={placeholder || ""}
value={value}
autoFocus={!!autoFocus}
onChange={(e) => onChange && onChange(e.target.value)}
disabled={!!disabled}
/>
{(value && onReset) && (
<button onClick={onReset} className="absolute inset-y-0 right-0 pr-3 flex items-center text-[var(--vscode-input-foreground)] hover:text-[var(--vscode-textLink-activeForeground)]">
<XCircleIcon className={`h-5 w-5`} aria-hidden="true" />
</button>
)}
</div>
{
description && (
<p className="text-xs text-[var(--vscode--settings-headerForeground)] opacity-75 mt-2 mx-2">
{description}
</p>
)
}
</>
);
};

View File

@@ -17,6 +17,8 @@ export const List: React.FunctionComponent<IListProps> = ({
className = `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-4`;
} else if (view === DashboardViewType.List) {
className = `-mx-4`;
} else if (view === DashboardViewType.Structure) {
className = `structure-view`;
}
return (

View File

@@ -9,6 +9,7 @@ import { GroupOption } from '../../constants/GroupOption';
import { GroupingSelector, PageAtom, PagedItems, ViewSelector } from '../../state';
import { Item } from './Item';
import { List } from './List';
import { StructureView } from './StructureView';
import usePagination from '../../hooks/usePagination';
import { LocalizationKey, localize } from '../../../localization';
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
@@ -145,16 +146,21 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
/>
{settings && settings?.contentFolders?.length > 0 ? (
<p className={`text-xl font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoMarkdown)}</p>
) : (
<p className={`text-lg font-medium`}>{localize(LocalizationKey.dashboardContentsOverviewNoFolders)}</p>
)}
</div>
</div>
);
}
// Handle Structure view first - it overrides all other display modes
if (view === DashboardViewType.Structure) {
return <StructureView pages={pages} />;
}
if (grouping !== GroupOption.none) {
return (
<>
@@ -196,7 +202,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({
<h1 className='text-xl flex space-x-2 items-center mb-4'>
<PinIcon className={`-rotate-45`} />
<span>{localize(LocalizationKey.dashboardContentsOverviewPinned)}</span>
</h1>
<List>
{pinnedPages.map((page, idx) => (

View File

@@ -0,0 +1,79 @@
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
import { Page } from '../../models/Page';
import { SettingsSelector } from '../../state';
import { DateField } from '../Common/DateField';
import { ContentActions } from './ContentActions';
import { useMemo } from 'react';
import { Status } from './Status';
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useCard from '../../hooks/useCard';
import { ItemSelection } from '../Common/ItemSelection';
import { openFile } from '../../utils';
import useSelectedItems from '../../hooks/useSelectedItems';
import { cn } from '../../../utils/cn';
export interface IStructureItemProps extends Page { }
export const StructureItem: React.FunctionComponent<IStructureItemProps> = ({
...pageData
}: React.PropsWithChildren<IStructureItemProps>) => {
const { selectedFiles } = useSelectedItems();
const settings = useRecoilValue(SettingsSelector);
const draftField = useMemo(() => settings?.draftField, [settings]);
const { escapedTitle } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
const isSelected = useMemo(() => selectedFiles.includes(pageData.fmFilePath), [selectedFiles, pageData.fmFilePath]);
const onOpenFile = React.useCallback(() => {
openFile(pageData.fmFilePath);
}, [pageData.fmFilePath]);
return (
<div className="relative">
<div
className={cn(
`flex items-center space-x-3 py-1 px-2 rounded cursor-pointer hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)]`,
isSelected && `bg-[var(--vscode-list-activeSelectionBackground)]`
)}
>
<ItemSelection filePath={pageData.fmFilePath} show />
<MarkdownIcon className="w-4 h-4 text-[var(--vscode-symbolIcon-fileForeground)] flex-shrink-0" />
<button
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
onClick={onOpenFile}
className="flex-1 text-left truncate font-medium"
>
{escapedTitle}
</button>
<div className="flex items-center space-x-2 flex-shrink-0">
{pageData.date && (
<DateField
value={pageData.date}
format={pageData.fmDateFormat}
className="text-xs text-[var(--vscode-descriptionForeground)]"
/>
)}
{draftField && draftField.name && typeof pageData[draftField.name] !== "undefined" && (
<Status draft={pageData[draftField.name]} published={pageData.fmPublished} />
)}
<ContentActions
path={pageData.fmFilePath}
relPath={pageData.fmRelFileWsPath}
contentType={pageData.fmContentType}
scripts={settings?.scripts}
onOpen={onOpenFile}
listView
/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,217 @@
import { Disclosure } from '@headlessui/react';
import { ChevronRightIcon, FolderIcon } from '@heroicons/react/24/solid';
import * as React from 'react';
import { useMemo } from 'react';
import { Page } from '../../models';
import { StructureItem } from './StructureItem';
import { parseWinPath } from '../../../helpers/parseWinPath';
export interface IStructureViewProps {
pages: Page[];
}
interface FolderNode {
name: string;
path: string;
children: FolderNode[];
pages: Page[];
}
export const StructureView: React.FunctionComponent<IStructureViewProps> = ({
pages
}: React.PropsWithChildren<IStructureViewProps>) => {
const folderTree = useMemo(() => {
const root: FolderNode = {
name: '',
path: '',
children: [],
pages: []
};
const folderMap = new Map<string, FolderNode>();
folderMap.set('', root);
// Helper to compute the normalized folder path for a page.
// It ensures the page's folder starts with the `fmFolder` segment and
// preserves any subpaths after that segment (so subfolders are created).
const computeNormalizedFolderPath = (page: Page): string => {
if (!page.fmFolder) {
return '';
}
const fmFolder = page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
// If we have a file path, use its directory (exclude the filename) to compute
// the relative path. This avoids treating filenames as folder segments.
const filePath = page.fmFilePath ? parseWinPath(page.fmFilePath).replace(/^\/+|\/+$/g, '') : '';
const fileDir = filePath && filePath.includes('/') ? filePath.substring(0, filePath.lastIndexOf('/')).replace(/^\/+|\/+$/g, '') : '';
if (fileDir) {
// If the content folder is known, and the file directory starts with it,
// replace that root with the fmFolder (preserving subfolders after it).
if (page.fmPageFolder?.path) {
const contentFolderPath = parseWinPath(page.fmPageFolder.path).replace(/^\/+|\/+$/g, '');
if (fileDir.startsWith(contentFolderPath)) {
const rel = fileDir.substring(contentFolderPath.length).replace(/^\/+|\/+$/g, '');
return rel ? `${fmFolder}/${rel}` : fmFolder;
}
}
// Otherwise try to find fmFolder as a directory segment in the fileDir
const segments = fileDir.split('/').filter(Boolean);
const fmIndex = segments.indexOf(fmFolder);
if (fmIndex >= 0) {
return segments.slice(fmIndex).join('/');
}
}
// Fallback: just use the fmFolder name
return fmFolder;
};
// First pass: create all folder nodes (ensure nodes exist even if a page lacks fmFilePath)
for (const page of pages) {
if (!page.fmFolder) {
continue;
}
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
if (!normalizedPath) {
continue;
}
const parts = normalizedPath.split('/').filter(part => part.length > 0);
let currentPath = '';
let currentNode = root;
for (const part of parts) {
const fullPath = currentPath ? `${currentPath}/${part}` : part;
if (!folderMap.has(fullPath)) {
const newNode: FolderNode = {
name: part,
path: fullPath,
children: [],
pages: []
};
folderMap.set(fullPath, newNode);
currentNode.children.push(newNode);
}
const nextNode = folderMap.get(fullPath);
if (nextNode) {
currentNode = nextNode;
}
currentPath = fullPath;
}
}
// Second pass: assign pages to their exact folder node (including subfolders)
for (const page of pages) {
if (!page.fmFolder) {
root.pages.push(page);
continue;
}
const normalizedPath = computeNormalizedFolderPath(page).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
const folderNode = normalizedPath ? folderMap.get(normalizedPath) : folderMap.get(page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''));
if (folderNode) {
folderNode.pages.push(page);
} else {
// If folder not found, add to root as fallback
root.pages.push(page);
}
}
return root;
}, [pages]);
const renderFolderNode = (node: FolderNode, depth = 0): React.ReactNode => {
const hasContent = node.pages.length > 0 || node.children.length > 0;
if (!hasContent) {
return null;
}
const isRoot = depth === 0;
const paddingLeft = depth * 20;
if (isRoot) {
// For root node, render children and pages directly
return (
<div className='space-y-4'>
{/* Root level folders */}
{node.children.map(child => renderFolderNode(child, depth + 1))}
{/* Root level pages */}
{node.pages.length > 0 && (
<div>
<h3 className="text-lg font-medium mb-3 text-[var(--vscode-editor-foreground)]">
Root Files
</h3>
<ul className="space-y-2">
{node.pages.map((page, idx) => (
<li key={`${page.slug}-${idx}`}>
<StructureItem {...page} />
</li>
))}
</ul>
</div>
)}
</div>
);
}
return (
<div key={node.path} className="mb-4">
<Disclosure defaultOpen={depth <= 1}>
{({ open }) => (
<>
<Disclosure.Button
className="flex items-center w-full text-left"
style={{ paddingLeft: `${paddingLeft}px` }}
>
<ChevronRightIcon
className={`w-4 h-4 mr-2 transform transition-transform ${open ? 'rotate-90' : ''
}`}
/>
<FolderIcon className="w-4 h-4 mr-2 text-[var(--vscode-symbolIcon-folderForeground)]" />
<span className="font-medium text-[var(--vscode-editor-foreground)]">
{node.name}
{node.pages.length > 0 && (
<span className="ml-2 text-sm text-[var(--vscode-descriptionForeground)]">
({node.pages.length} {node.pages.length === 1 ? 'file' : 'files'})
</span>
)}
</span>
</Disclosure.Button>
<Disclosure.Panel className="mt-2">
{/* Child folders */}
{node.children.map(child => renderFolderNode(child, depth + 1))}
{/* Pages in this folder */}
{node.pages.length > 0 && (
<ul className="space-y-1 mb-3">
{node.pages.map((page, idx) => (
<li key={`${page.slug}-${idx}`} style={{ paddingLeft: `${paddingLeft + 20}px` }}>
<StructureItem {...page} />
</li>
))}
</ul>
)}
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
);
};
return (
<div className="structure-view">
{renderFolderNode(folderTree)}
</div>
);
};

View File

@@ -2,10 +2,11 @@ import * as React from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import usePagination from '../../hooks/usePagination';
import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state';
import { MediaTotalSelector, PageAtom, SettingsAtom, ViewSelector } from '../../state';
import { PaginationButton } from './PaginationButton';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { DashboardViewType } from '../../models';
export interface IPaginationProps {
totalPages?: number;
@@ -17,6 +18,7 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
const [page, setPage] = useRecoilState(PageAtom);
const totalMedia = useRecoilValue(MediaTotalSelector);
const settings = useRecoilValue(SettingsAtom);
const view = useRecoilValue(ViewSelector);
const { pageSetNr, totalPagesNr } = usePagination(
settings?.dashboardState.contents.pagination,
totalPages,
@@ -33,17 +35,17 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
if (i >= 0 && i <= totalPagesNr) {
buttons.push(
<button
key={i}
disabled={i === page}
onClick={() => {
setPage(i);
}}
className={`max-h-8 rounded ${page === i
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
>
{i + 1}
</button>
key={i}
disabled={i === page}
onClick={() => {
setPage(i);
}}
className={`max-h-8 rounded ${page === i
? `px-2 bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]`
: `text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}`}
>
{i + 1}
</button>
);
}
}
@@ -58,6 +60,10 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({
setPage(0);
}, []);
if (view === DashboardViewType.Structure) {
return null;
}
return (
<div className="flex justify-between items-center sm:justify-end space-x-2 text-sm">
<PaginationButton

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ViewAtom, SettingsSelector } from '../../state';
import { Bars4Icon, Squares2X2Icon } from '@heroicons/react/24/solid';
import { Bars4Icon, Squares2X2Icon, FolderIcon } from '@heroicons/react/24/solid';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { DashboardViewType } from '../../models';
@@ -16,9 +16,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
const [view, setView] = useRecoilState(ViewAtom);
const settings = useRecoilValue(SettingsSelector);
const toggleView = () => {
const newView =
view === DashboardViewType.Grid ? DashboardViewType.List : DashboardViewType.Grid;
const handleViewChange = (newView: DashboardViewType) => {
setView(newView);
Messenger.send(DashboardMessage.setPageViewType, newView);
};
@@ -36,7 +34,7 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToGrid)}
type={`button`}
onClick={toggleView}
onClick={() => handleViewChange(DashboardViewType.Grid)}
>
<Squares2X2Icon className={`w-4 h-4`} />
<span className={`sr-only`}>
@@ -44,17 +42,29 @@ export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (
</span>
</button>
<button
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
className={`flex items-center px-2 py-1 ${view === DashboardViewType.List ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
type={`button`}
onClick={toggleView}
onClick={() => handleViewChange(DashboardViewType.List)}
>
<Bars4Icon className={`w-4 h-4`} />
<span className={`sr-only`}>
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToList)}
</span>
</button>
<button
className={`flex items-center px-2 py-1 rounded-r-sm ${view === DashboardViewType.Structure ? `bg-[var(--frontmatter-button-background)] text-[var(--vscode-button-foreground)]` : 'text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]'
}`}
title={l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
type={`button`}
onClick={() => handleViewChange(DashboardViewType.Structure)}
>
<FolderIcon className={`w-4 h-4`} />
<span className={`sr-only`}>
{l10n.t(LocalizationKey.dashboardHeaderViewSwitchToStructure)}
</span>
</button>
</div>
);
};

View File

@@ -3,6 +3,7 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline';
import { Choice, SnippetField, SnippetInfoField } from '../../../models';
import { useEffect } from 'react';
import { TextField } from '../Common/TextField';
import { NumberField } from '../Common/NumberField';
export interface ISnippetInputFieldProps {
field: SnippetField;
@@ -78,6 +79,17 @@ export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps>
);
}
if (field.type === 'number') {
return (
<NumberField
name={field.name}
value={field.value as string || ''}
description={field.description}
onChange={(e) => onValueChange(field, e)}
/>
);
}
return (
<TextField
name={field.name}

View File

@@ -21,9 +21,7 @@ import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatu
export interface ISnippetsProps { }
export const Snippets: React.FunctionComponent<ISnippetsProps> = (
_: React.PropsWithChildren<ISnippetsProps>
) => {
export const Snippets: React.FunctionComponent<ISnippetsProps> = () => {
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const mode = useRecoilValue(ModeAtom);

View File

@@ -1,4 +1,5 @@
export enum DashboardViewType {
Grid = 1,
List
List,
Structure
}

View File

@@ -1,4 +1,4 @@
import { I18nConfig } from '../../models';
import { ContentFolder, I18nConfig } from '../../models';
export interface Page {
// Properties for caching
@@ -20,15 +20,16 @@ export interface Page {
fmCategories: string[];
fmContentType: string;
fmDateFormat: string | undefined;
fmPageFolder: ContentFolder | undefined;
// i18n fields
fmDefaultLocale?: boolean;
fmLocale?: I18nConfig;
fmTranslations?: {
fmTranslations?: {
[locale: string]: {
locale: I18nConfig;
path: string;
}
};
};
title: string;

View File

@@ -727,6 +727,10 @@ export enum LocalizationKey {
* Change to list
*/
dashboardHeaderViewSwitchToList = 'dashboard.header.viewSwitch.toList',
/**
* Change to structure
*/
dashboardHeaderViewSwitchToStructure = 'dashboard.header.viewSwitch.toStructure',
/**
* Support Front Matter
*/
@@ -1108,7 +1112,7 @@ export enum LocalizationKey {
*/
dashboardStepsStepsToGetStartedGitName = 'dashboard.steps.stepsToGetStarted.git.name',
/**
* Enable Git synchronization to eaily sync your changes with your repository.
* Enable Git synchronization to easily sync your changes with your repository.
*/
dashboardStepsStepsToGetStartedGitDescription = 'dashboard.steps.stepsToGetStarted.git.description',
/**

View File

@@ -40,11 +40,13 @@ export const DateTimeField: React.FunctionComponent<IDateTimeFieldProps> = ({
const onDateChange = React.useCallback((date: Date) => {
setDateValue(date);
if (format) {
// Always use DateHelper.formatInTimezone when a format is provided
onChange(DateHelper.formatInTimezone(date, format, timezone) || "");
} else {
// Only fallback to ISO string if no format is provided
onChange(date.toISOString());
}
}, [format, onChange]);
}, [format, timezone, onChange]);
const showRequiredState = useMemo(() => {
return required && !dateValue;

View File

@@ -33,7 +33,8 @@ export class Copilot {
}
const copilotExt = extensions.getExtension(`GitHub.copilot`);
return !!copilotExt;
const copilotChatExt = extensions.getExtension(`GitHub.copilot-chat`);
return !!copilotExt || !!copilotChatExt;
}
public static async suggestTitles(title: string): Promise<string[] | undefined> {
@@ -269,7 +270,7 @@ Example: SEO, website optimization, digital marketing.`
// console.log(models);
const [model] = await lm.selectChatModels({
vendor: 'copilot',
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4o-mini'
family: Settings.get<string>(SETTING_COPILOT_FAMILY) || 'gpt-4.1'
});
if ((!model || !model.sendRequest) && retry <= 5) {

View File

@@ -219,6 +219,7 @@ export class PagesParser {
const isDefaultLanguage = await i18n.isDefaultLanguage(filePath);
const locale = await i18n.getLocale(filePath);
const translations = await i18n.getTranslations(filePath);
const pageFolder = await Folders.getPageFolderByFilePath(filePath);
const page: Page = {
...article.data,
@@ -241,6 +242,7 @@ export class PagesParser {
fmContentType: contentType.name || DEFAULT_CONTENT_TYPE_NAME,
fmBody: article?.content || '',
fmDateFormat: dateFormat,
fmPageFolder: pageFolder,
// i18n properties
fmDefaultLocale: isDefaultLanguage,
fmLocale: locale,