diff --git a/package.json b/package.json index edee295a..fc721623 100644 --- a/package.json +++ b/package.json @@ -2246,6 +2246,11 @@ "title": "%command.frontMatter.createContent%", "category": "Front Matter" }, + { + "command": "frontMatter.structure.createContentInFolder", + "title": "Create Content in Folder", + "category": "Front Matter" + }, { "command": "frontMatter.createTag", "title": "%command.frontMatter.createTag%", @@ -2484,6 +2489,11 @@ { "command": "workbench.action.webview.openDeveloperTools", "when": "frontMatter:isDevelopment" + }, + { + "command": "frontMatter.structure.createContentInFolder", + "when": "webview == frontMatterDashboard && webviewItem == folder", + "group": "1_structure@1" } ], "editor/title": [ diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index aa935517..4fb282d5 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -24,6 +24,7 @@ export const COMMAND_NAME = { createByContentType: getCommandName('createByContentType'), createByTemplate: getCommandName('createByTemplate'), createContentInFolder: getCommandName('createContentInFolder'), + structureCreateContentInFolder: getCommandName('structure.createContentInFolder'), createTemplate: getCommandName('createTemplate'), initTemplate: getCommandName('initTemplate'), collapseSections: getCommandName('collapseSections'), diff --git a/src/dashboardWebView/components/Contents/StructureView.tsx b/src/dashboardWebView/components/Contents/StructureView.tsx index d883c2c8..1b4187ce 100644 --- a/src/dashboardWebView/components/Contents/StructureView.tsx +++ b/src/dashboardWebView/components/Contents/StructureView.tsx @@ -1,7 +1,7 @@ import { Disclosure } from '@headlessui/react'; import { ChevronRightIcon, FolderIcon, PlusIcon } from '@heroicons/react/24/solid'; import * as React from 'react'; -import { useMemo } from 'react'; +import { useMemo, useState, useCallback } from 'react'; import { Page } from '../../models'; import { StructureItem } from './StructureItem'; import { parseWinPath } from '../../../helpers/parseWinPath'; @@ -19,9 +19,22 @@ interface FolderNode { pages: Page[]; } +interface ContextMenuState { + visible: boolean; + x: number; + y: number; + folderPath: string; +} + export const StructureView: React.FunctionComponent = ({ pages }: React.PropsWithChildren) => { + const [contextMenu, setContextMenu] = useState({ + visible: false, + x: 0, + y: 0, + folderPath: '' + }); const createContentInFolder = React.useCallback((folderPath: string, nodePagesOnly: Page[]) => { // Find a page from this folder to get the base content folder information @@ -31,7 +44,9 @@ export const StructureView: React.FunctionComponent = ({ 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; + if (!page.fmFolder || !page.fmPageFolder) { + return false; + } const normalizedFmFolder = page.fmFolder.replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''); return folderPath.startsWith(normalizedFmFolder) || normalizedFmFolder.startsWith(folderPath.split('/')[0]); }); @@ -47,6 +62,38 @@ export const StructureView: React.FunctionComponent = ({ } }, [pages]); + const handleContextMenu = useCallback((e: React.MouseEvent, folderPath: string) => { + e.preventDefault(); + e.stopPropagation(); + + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + folderPath + }); + }, []); + + const hideContextMenu = useCallback(() => { + setContextMenu(prev => ({ ...prev, visible: false })); + }, []); + + const handleCreateContent = useCallback(() => { + if (contextMenu.folderPath) { + createContentInFolder(contextMenu.folderPath, []); + } + hideContextMenu(); + }, [contextMenu.folderPath, createContentInFolder, hideContextMenu]); + + // Close context menu when clicking outside + React.useEffect(() => { + const handleClick = () => hideContextMenu(); + if (contextMenu.visible) { + document.addEventListener('click', handleClick); + return () => document.removeEventListener('click', handleClick); + } + }, [contextMenu.visible, hideContextMenu]); + const folderTree = useMemo(() => { const root: FolderNode = { name: '', @@ -195,7 +242,13 @@ export const StructureView: React.FunctionComponent = ({ {({ open }) => ( <> -
+
handleContextMenu(e, node.path)} + data-webview-item="folder" + data-webview-item-element="name" + data-folder-path={node.path} + > = ({ return (
{renderFolderNode(folderTree)} + + {/* Custom Context Menu */} + {contextMenu.visible && ( +
e.stopPropagation()} + > + +
+ )}
); }; \ No newline at end of file diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 48040de9..680f3adc 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -59,6 +59,10 @@ export class ContentType { commands.registerCommand(COMMAND_NAME.createContentInFolder, ContentType.createContentInFolder) ); + subscriptions.push( + commands.registerCommand(COMMAND_NAME.structureCreateContentInFolder, ContentType.structureCreateContentInFolder) + ); + subscriptions.push( commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate) ); @@ -187,6 +191,28 @@ export class ContentType { } } + /** + * Create content in a specific folder from Structure view context menu + * @param webviewContext - The webview context data containing folder path + */ + public static async structureCreateContentInFolder(webviewContext?: any) { + let folderPath: string | undefined; + + // VS Code webview context menu passes the element's data attributes + // The data-folder-path attribute will be available in the context + if (webviewContext) { + folderPath = webviewContext.folderPath || webviewContext['folder-path']; + } + + if (!folderPath) { + Notifications.warning('Unable to determine folder path for content creation.'); + return; + } + + // Reuse the existing createContentInFolder logic + await ContentType.createContentInFolder({ folderPath }); + } + /** * Retrieve all content types * @returns