mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
Add click-to-create content in specific folders for Structure view
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
This commit is contained in:
@@ -23,6 +23,7 @@ export const COMMAND_NAME = {
|
||||
createContent: getCommandName('createContent'),
|
||||
createByContentType: getCommandName('createByContentType'),
|
||||
createByTemplate: getCommandName('createByTemplate'),
|
||||
createContentInFolder: getCommandName('createContentInFolder'),
|
||||
createTemplate: getCommandName('createTemplate'),
|
||||
initTemplate: getCommandName('initTemplate'),
|
||||
collapseSections: getCommandName('collapseSections'),
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum DashboardMessage {
|
||||
createContent = 'createContent',
|
||||
createByContentType = 'createByContentType',
|
||||
createByTemplate = 'createByTemplate',
|
||||
createContentInFolder = 'createContentInFolder',
|
||||
refreshPages = 'refreshPages',
|
||||
searchPages = 'searchPages',
|
||||
openFile = 'openFile',
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import { ChevronRightIcon, FolderIcon } from '@heroicons/react/24/solid';
|
||||
import { ChevronRightIcon, FolderIcon, PlusIcon } 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';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
|
||||
export interface IStructureViewProps {
|
||||
pages: Page[];
|
||||
@@ -20,6 +22,31 @@ interface FolderNode {
|
||||
export const StructureView: React.FunctionComponent<IStructureViewProps> = ({
|
||||
pages
|
||||
}: React.PropsWithChildren<IStructureViewProps>) => {
|
||||
|
||||
const createContentInFolder = React.useCallback((folderPath: string, nodePagesOnly: Page[]) => {
|
||||
// Find a page from this folder to get the base content folder information
|
||||
// First try to find from the specific folder, then from all pages if not found
|
||||
let samplePage = nodePagesOnly.find(page => page.fmPageFolder);
|
||||
|
||||
if (!samplePage) {
|
||||
// If no pages in this specific folder, find any page that has the same base folder structure
|
||||
samplePage = pages.find(page => {
|
||||
if (!page.fmFolder || !page.fmPageFolder) return false;
|
||||
const normalizedFmFolder = page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
||||
return folderPath.startsWith(normalizedFmFolder) || normalizedFmFolder.startsWith(folderPath.split('/')[0]);
|
||||
});
|
||||
}
|
||||
|
||||
if (samplePage && samplePage.fmPageFolder) {
|
||||
// Construct the full folder path by combining the base content folder with the structure path
|
||||
const baseFolderPath = samplePage.fmPageFolder.path.replace(/\\/g, '/').replace(/\/+$/, '');
|
||||
const relativePath = folderPath.replace(/^\/+|\/+$/g, '');
|
||||
const fullFolderPath = `${baseFolderPath}/${relativePath}`;
|
||||
|
||||
messageHandler.send(DashboardMessage.createContentInFolder, { folderPath: fullFolderPath });
|
||||
}
|
||||
}, [pages]);
|
||||
|
||||
const folderTree = useMemo(() => {
|
||||
const root: FolderNode = {
|
||||
name: '',
|
||||
@@ -168,24 +195,37 @@ export const StructureView: React.FunctionComponent<IStructureViewProps> = ({
|
||||
<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>
|
||||
<div className="flex items-center justify-between w-full group">
|
||||
<Disclosure.Button
|
||||
className="flex items-center flex-1 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>
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
createContentInFolder(node.path, node.pages);
|
||||
}}
|
||||
className="opacity-0 group-hover:opacity-100 p-1 ml-2 mr-2 rounded hover:bg-[var(--vscode-list-hoverBackground)] transition-opacity"
|
||||
title="Create content in this folder"
|
||||
>
|
||||
<PlusIcon className="w-4 h-4 text-[var(--vscode-editor-foreground)]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Disclosure.Panel className="mt-2">
|
||||
{/* Child folders */}
|
||||
|
||||
@@ -55,6 +55,10 @@ export class ContentType {
|
||||
commands.registerCommand(COMMAND_NAME.createByContentType, ContentType.createContent)
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.createContentInFolder, ContentType.createContentInFolder)
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate)
|
||||
);
|
||||
@@ -144,6 +148,45 @@ export class ContentType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content in a specific folder based on content types
|
||||
* @param folderData - Object containing folder path information
|
||||
* @returns
|
||||
*/
|
||||
public static async createContentInFolder(folderData: { folderPath: string }) {
|
||||
if (!folderData || !folderData.folderPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentTypes = ContentType.getAll();
|
||||
let folders = await Folders.get();
|
||||
folders = folders.filter((f) => !f.disableCreation);
|
||||
|
||||
// Find the folder that matches the provided path
|
||||
const folder = folders.find((f) => {
|
||||
const folderPath = Folders.getFolderPath(Uri.file(f.path));
|
||||
// Check if the folderData.folderPath is within this content folder
|
||||
return folderData.folderPath.includes(folderPath || '');
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedContentType = await Questions.SelectContentType(folder.contentTypes || []);
|
||||
if (!selectedContentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentTypes && folder) {
|
||||
const contentType = contentTypes.find((ct) => ct.name === selectedContentType);
|
||||
if (contentType) {
|
||||
// Use the specific folder path provided instead of the base folder path
|
||||
ContentType.create(contentType, folderData.folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all content types
|
||||
* @returns
|
||||
|
||||
@@ -45,6 +45,9 @@ export class PagesListener extends BaseListener {
|
||||
case DashboardMessage.createByTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createByTemplate);
|
||||
break;
|
||||
case DashboardMessage.createContentInFolder:
|
||||
await commands.executeCommand(COMMAND_NAME.createContentInFolder, msg.payload);
|
||||
break;
|
||||
case DashboardMessage.refreshPages:
|
||||
this.getPagesData(true);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user