mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-05 01:11:19 +02:00
#785 - Added custom script actions to the bottom of the cards
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { EyeIcon, GlobeEuropeAfricaIcon, TrashIcon, LanguageIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { CustomScript, I18nConfig, ScriptType } from '../../../models';
|
||||
import { CustomScript, I18nConfig } from '../../../models';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
@@ -10,13 +10,16 @@ import { SelectedItemActionAtom, SettingsSelector } from '../../state';
|
||||
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
|
||||
import { PinIcon } from '../Icons/PinIcon';
|
||||
import { PinnedItemsAtom } from '../../state/atom/PinnedItems';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { RenameIcon } from '../../../components/icons/RenameIcon';
|
||||
import { openFile, openOnWebsite } from '../../utils';
|
||||
import { openOnWebsite } from '../../utils';
|
||||
import { CustomActions } from './CustomActions';
|
||||
import { TranslationMenu } from './TranslationMenu';
|
||||
|
||||
export interface IContentActionsProps {
|
||||
path: string;
|
||||
relPath: string;
|
||||
contentType: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
listView?: boolean;
|
||||
locale?: I18nConfig;
|
||||
@@ -33,6 +36,7 @@ export interface IContentActionsProps {
|
||||
export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
path,
|
||||
relPath,
|
||||
contentType,
|
||||
scripts,
|
||||
onOpen,
|
||||
listView,
|
||||
@@ -78,14 +82,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
})
|
||||
}, [path]);
|
||||
|
||||
const runCustomScript = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
Messenger.send(DashboardMessage.runCustomScript, { script, path });
|
||||
},
|
||||
[path]
|
||||
);
|
||||
|
||||
const runCommand = React.useCallback((commandId: string) => {
|
||||
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
|
||||
command: commandId,
|
||||
@@ -97,65 +93,6 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
return pinnedItems.includes(relPath);
|
||||
}, [pinnedItems, relPath]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
return (scripts || [])
|
||||
.filter(
|
||||
(script) =>
|
||||
(script.type === undefined || script.type === ScriptType.Content) &&
|
||||
!script.bulk &&
|
||||
!script.hidden
|
||||
)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem key={script.id || script.title} onClick={(e) => runCustomScript(e, script)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
));
|
||||
}, [scripts]);
|
||||
|
||||
const translationsMenu = React.useMemo(() => {
|
||||
if (!locale || !translations || Object.keys(translations).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const crntLocale = translations[locale.locale];
|
||||
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
|
||||
|
||||
if (otherLocales.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => openFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
}, [translations, locale, isDefaultLocale]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -208,9 +145,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
{translationsMenu}
|
||||
<TranslationMenu
|
||||
isDefaultLocale={isDefaultLocale}
|
||||
locale={locale}
|
||||
translations={translations} />
|
||||
|
||||
{customScriptActions}
|
||||
<CustomActions
|
||||
filePath={path}
|
||||
contentType={contentType}
|
||||
scripts={scripts} />
|
||||
|
||||
<DropdownMenuItem onClick={onDelete} className={`focus:bg-[var(--vscode-statusBarItem-errorBackground)] focus:text-[var(--vscode-statusBarItem-errorForeground)]`}>
|
||||
<TrashIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { CommandLineIcon } from '@heroicons/react/24/outline';
|
||||
import { CommandLineIcon as CommandLineIconSolid } from '@heroicons/react/24/solid';
|
||||
import { runCustomScript } from '../../utils';
|
||||
import { CustomScript, ScriptType } from '../../../models';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ICustomActionsProps {
|
||||
filePath: string;
|
||||
contentType: string;
|
||||
scripts: CustomScript[] | undefined;
|
||||
showTrigger?: boolean;
|
||||
}
|
||||
|
||||
export const CustomActions: React.FunctionComponent<ICustomActionsProps> = ({
|
||||
filePath,
|
||||
contentType,
|
||||
scripts,
|
||||
showTrigger = false,
|
||||
}: React.PropsWithChildren<ICustomActionsProps>) => {
|
||||
|
||||
const onRunCustomScript = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>, script: CustomScript) => {
|
||||
e.stopPropagation();
|
||||
runCustomScript(script, filePath);
|
||||
},
|
||||
[filePath]
|
||||
);
|
||||
|
||||
const customScripts = React.useMemo(() => {
|
||||
return (scripts || []).filter((script: CustomScript) => {
|
||||
if (script.contentTypes && script.contentTypes.length > 0) {
|
||||
return script.contentTypes.includes(contentType);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [scripts, contentType]);
|
||||
|
||||
const customActions = React.useMemo(() => {
|
||||
if (!customScripts || customScripts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(customScripts || [])
|
||||
.filter(
|
||||
(script) =>
|
||||
(script.type === undefined || script.type === ScriptType.Content) &&
|
||||
!script.bulk &&
|
||||
!script.hidden
|
||||
)
|
||||
.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
title={script.title}
|
||||
onClick={(e) => onRunCustomScript(e, script)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
);
|
||||
}, [customScripts, onRunCustomScript]);
|
||||
|
||||
if (!customActions || customActions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (showTrigger) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
title={l10n.t(LocalizationKey.commonOpenCustomActions)}
|
||||
className='px-2 text-[var(--frontmatter-secondary-text)] hover:text-[var(--frontmatter-button-hoverBackground)] focus-visible:outline-none'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonOpenCustomActions)}</span>
|
||||
<CommandLineIconSolid className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{customActions}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{customActions}</>;
|
||||
};
|
||||
@@ -6,16 +6,21 @@ import { LocalizationKey } from '../../../localization';
|
||||
import { openFile, openOnWebsite } from '../../utils';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
// import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { CustomScript } from '../../../models';
|
||||
import { CustomActions } from './CustomActions';
|
||||
|
||||
export interface IFooterActionsProps {
|
||||
filePath: string;
|
||||
contentType: string;
|
||||
websiteUrl?: string;
|
||||
scripts?: CustomScript[];
|
||||
}
|
||||
|
||||
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
filePath,
|
||||
websiteUrl
|
||||
contentType,
|
||||
websiteUrl,
|
||||
scripts
|
||||
}: React.PropsWithChildren<IFooterActionsProps>) => {
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
@@ -43,6 +48,12 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
<CustomActions
|
||||
filePath={filePath}
|
||||
contentType={contentType}
|
||||
scripts={scripts}
|
||||
showTrigger />
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonDelete)}
|
||||
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
|
||||
@@ -152,6 +152,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<ContentActions
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
locale={pageData.fmLocale}
|
||||
isDefaultLocale={pageData.fmDefaultLocale}
|
||||
translations={pageData.fmTranslations}
|
||||
@@ -210,7 +211,9 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
<FooterActions
|
||||
filePath={pageData.fmFilePath}
|
||||
websiteUrl={settings?.websiteUrl} />
|
||||
contentType={pageData.fmContentType}
|
||||
websiteUrl={settings?.websiteUrl}
|
||||
scripts={settings?.scripts} />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
@@ -232,6 +235,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
<ContentActions
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={onOpenFile}
|
||||
listView
|
||||
|
||||
@@ -54,6 +54,7 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
<ContentActions
|
||||
path={pageData.fmFilePath}
|
||||
relPath={pageData.fmRelFileWsPath}
|
||||
contentType={pageData.fmContentType}
|
||||
scripts={settings?.scripts}
|
||||
onOpen={openFile}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '../../../components/shadcn/Dropdown';
|
||||
import { LanguageIcon } from '@heroicons/react/24/outline';
|
||||
import { openFile } from '../../utils/MessageHandlers';
|
||||
import { I18nConfig } from '../../../models';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
|
||||
export interface ITranslationMenuProps {
|
||||
isDefaultLocale?: boolean;
|
||||
locale?: I18nConfig;
|
||||
translations?: {
|
||||
[locale: string]: {
|
||||
locale: I18nConfig;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const TranslationMenu: React.FunctionComponent<ITranslationMenuProps> = ({
|
||||
isDefaultLocale,
|
||||
locale,
|
||||
translations,
|
||||
}: React.PropsWithChildren<ITranslationMenuProps>) => {
|
||||
|
||||
const otherLocales = React.useMemo(() => {
|
||||
if (!translations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(translations).filter(([key]) => key !== locale?.locale);
|
||||
}, [translations]);
|
||||
|
||||
const crntLocale = React.useMemo(() => {
|
||||
if (!locale?.locale || !translations || !translations[locale.locale]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return translations[locale.locale];
|
||||
}, [translations, locale]);
|
||||
|
||||
|
||||
if (!locale || !translations || Object.keys(translations).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (otherLocales.length === 0 || !crntLocale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardContentsContentActionsTranslationsMenu)}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => openFile(crntLocale.path)}>
|
||||
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{
|
||||
otherLocales.map(([key, value]) => (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => openFile(value.path)}
|
||||
>
|
||||
<span>{value.locale.title || value.locale.locale}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user