mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-03 16:31:32 +02:00
#824 - Added support in single image field
This commit is contained in:
+13
-9
@@ -1550,8 +1550,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"$ref": "#customscript"
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.actions.description%",
|
||||
"items": {
|
||||
"$ref": "#customscript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -2659,23 +2663,23 @@
|
||||
"group": "navigation@0",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"group": "navigation@1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.mode.switch",
|
||||
"group": "navigation@2",
|
||||
"group": "navigation@1",
|
||||
"when": "view == frontMatter.explorer && frontMatter:has:modes == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"group": "navigation@3",
|
||||
"group": "navigation@2",
|
||||
"when": "view == frontMatter.explorer && frontMatter:project:switch:enabled"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.settings.refresh",
|
||||
"group": "navigation@3",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.collapseSections",
|
||||
"group": "navigation@4",
|
||||
"when": "view == frontMatter.explorer"
|
||||
},
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.operator.description": "The operator to use",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.value.description": "The value to compare",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description": "Specify if the comparison is case sensitive. Default: true",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.actions.description": "Specify the field custom actions",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.pageBundle.description": "Specify if you want to create a folder when creating new content.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.previewPath.description": "Defines a custom preview path for the content type.",
|
||||
"setting.frontMatter.taxonomy.contentTypes.items.properties.trailingSlash.description": "Specify if you want to add a trailing slash to the preview URL.",
|
||||
|
||||
@@ -143,7 +143,7 @@ export interface Field {
|
||||
when?: WhenClause;
|
||||
|
||||
// Custom action
|
||||
action?: CustomScript;
|
||||
actions?: CustomScript[];
|
||||
}
|
||||
|
||||
export interface NumberOptions {
|
||||
|
||||
@@ -1,48 +1,85 @@
|
||||
import * as React from 'react';
|
||||
import { CustomScript } from '../../../models';
|
||||
import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import { CodeBracketIcon } from '@heroicons/react/24/outline';
|
||||
import { CodeBracketIcon, CommandLineIcon } from '@heroicons/react/24/solid';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { LocalizationKey, localize } from '../../../localization';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
|
||||
|
||||
export interface IFieldCustomActionProps {
|
||||
action: CustomScript;
|
||||
actions: CustomScript[];
|
||||
disabled?: boolean;
|
||||
triggerLoading?: (message?: string) => void;
|
||||
onChange: (value: any) => void;
|
||||
}
|
||||
|
||||
export const FieldCustomAction: React.FunctionComponent<IFieldCustomActionProps> = ({ action, disabled, triggerLoading, onChange }: React.PropsWithChildren<IFieldCustomActionProps>) => {
|
||||
export const FieldCustomAction: React.FunctionComponent<IFieldCustomActionProps> = ({ actions, disabled, triggerLoading, onChange }: React.PropsWithChildren<IFieldCustomActionProps>) => {
|
||||
|
||||
const triggerAction = React.useCallback((action: CustomScript) => {
|
||||
if (triggerLoading) {
|
||||
triggerLoading(localize(LocalizationKey.panelFieldsFieldCustomActionExecuting));
|
||||
}
|
||||
|
||||
messageHandler.request(CommandToCode.runFieldAction, {
|
||||
...action
|
||||
}).then((value: any) => {
|
||||
onChange(value);
|
||||
|
||||
if (triggerLoading) {
|
||||
triggerLoading();
|
||||
}
|
||||
}).catch(() => {
|
||||
console.error('Error while running the custom action');
|
||||
|
||||
if (triggerLoading) {
|
||||
triggerLoading();
|
||||
}
|
||||
});
|
||||
}, [triggerLoading, onChange]);
|
||||
|
||||
if (!actions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (actions.length === 1) {
|
||||
const action = actions[0];
|
||||
return (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
title={action?.title || localize(LocalizationKey.panelFieldsFieldCustomActionButtonTitle)}
|
||||
type="button"
|
||||
onClick={triggerAction.bind(null, action)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className='sr-only'>{action?.title || localize(LocalizationKey.panelFieldsFieldCustomActionButtonTitle)}</span>
|
||||
<CommandLineIcon style={{ height: "16px", width: "16px" }} aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
title={action?.title || localize(LocalizationKey.panelFieldsFieldCustomActionButtonTitle)}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (triggerLoading) {
|
||||
triggerLoading(localize(LocalizationKey.panelFieldsFieldCustomActionExecuting));
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
title={localize(LocalizationKey.commonOpenCustomActions)}
|
||||
className='metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50'>
|
||||
<span className="sr-only">{localize(LocalizationKey.commonOpenCustomActions)}</span>
|
||||
<CommandLineIcon style={{ height: "16px", width: "16px" }} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='end' className='p-0'>
|
||||
{
|
||||
actions.map((action) => (
|
||||
<DropdownMenuItem
|
||||
key={action.id || action.title}
|
||||
title={action.title}
|
||||
className={`focus:bg-[var(--vscode-button-background)] focus:text-[var(--vscode-button-foreground)] focus:outline-0 rounded-none`}
|
||||
onClick={(e) => triggerAction(action)}>
|
||||
<CommandLineIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
|
||||
<span>{action.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
|
||||
messageHandler.request(CommandToCode.runFieldAction, {
|
||||
...action
|
||||
}).then((value: any) => {
|
||||
onChange(value);
|
||||
|
||||
if (triggerLoading) {
|
||||
triggerLoading();
|
||||
}
|
||||
}).catch(() => {
|
||||
console.error('Error while running the custom action');
|
||||
|
||||
if (triggerLoading) {
|
||||
triggerLoading();
|
||||
}
|
||||
});
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className='sr-only'>{action?.title || localize(LocalizationKey.panelFieldsFieldCustomActionButtonTitle)}</span>
|
||||
<CodeBracketIcon style={{ height: "16px", width: "16px" }} aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
};
|
||||
@@ -10,7 +10,7 @@ export interface IFieldTitleProps {
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
actionElement?: JSX.Element;
|
||||
customAction?: CustomScript;
|
||||
customActions?: CustomScript[];
|
||||
isDisabled?: boolean;
|
||||
triggerLoading?: (message?: string) => void;
|
||||
onChange?: (value: any) => void;
|
||||
@@ -22,7 +22,7 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
className,
|
||||
required,
|
||||
actionElement,
|
||||
customAction,
|
||||
customActions,
|
||||
isDisabled,
|
||||
triggerLoading,
|
||||
onChange,
|
||||
@@ -40,17 +40,17 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
</label>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{actionElement}
|
||||
|
||||
{
|
||||
customAction && onChange && (
|
||||
customActions && onChange && (
|
||||
<FieldCustomAction
|
||||
action={customAction}
|
||||
actions={customActions}
|
||||
disabled={isDisabled}
|
||||
triggerLoading={triggerLoading}
|
||||
onChange={onChange} />
|
||||
)
|
||||
}
|
||||
|
||||
{actionElement}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PhotoIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { DefaultFieldValues } from '../../../constants';
|
||||
import { BaseFieldProps, BlockFieldData } from '../../../models';
|
||||
import { BaseFieldProps, BlockFieldData, CustomScript } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { FieldTitle } from './FieldTitle';
|
||||
import { PreviewImage } from './PreviewImage';
|
||||
@@ -23,6 +23,7 @@ export interface IPreviewImageFieldProps
|
||||
parents?: string[];
|
||||
multiple?: boolean;
|
||||
blockData?: BlockFieldData;
|
||||
actions?: CustomScript[];
|
||||
onChange: (value: string | string[] | null) => void;
|
||||
}
|
||||
|
||||
@@ -36,8 +37,10 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
|
||||
filePath,
|
||||
multiple,
|
||||
parents,
|
||||
actions,
|
||||
required
|
||||
}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
|
||||
const [loading, setLoading] = React.useState<string | undefined>(undefined);
|
||||
const [imageData, setImageData] = React.useState<PreviewImageValue | PreviewImageValue[] | null>(null);
|
||||
|
||||
const selectImage = useCallback(() => {
|
||||
@@ -95,7 +98,14 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
<FieldTitle label={label} icon={<PhotoIcon />} required={required} />
|
||||
<FieldTitle
|
||||
label={label}
|
||||
icon={<PhotoIcon />}
|
||||
required={required}
|
||||
isDisabled={!!loading}
|
||||
customActions={actions}
|
||||
triggerLoading={(message) => setLoading(message)}
|
||||
onChange={(value: string) => onChange(value)} />
|
||||
|
||||
<div
|
||||
className={`metadata_field__preview_image ${multiple && imageData && (imageData as PreviewImageValue[]).length > 0
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface ITagPickerProps {
|
||||
limit?: number;
|
||||
required?: boolean;
|
||||
renderAsString?: boolean;
|
||||
action?: CustomScript;
|
||||
actions?: CustomScript[];
|
||||
}
|
||||
|
||||
const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
@@ -57,7 +57,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
limit,
|
||||
required,
|
||||
renderAsString,
|
||||
action
|
||||
actions
|
||||
}: React.PropsWithChildren<ITagPickerProps>) => {
|
||||
const [selected, setSelected] = React.useState<string[]>([]);
|
||||
const [inputValue, setInputValue] = React.useState<string>('');
|
||||
@@ -393,7 +393,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
icon={icon}
|
||||
required={required}
|
||||
isDisabled={!!loading}
|
||||
customAction={action}
|
||||
customActions={actions}
|
||||
triggerLoading={(message) => setLoading(message)}
|
||||
onChange={updateTaxonomy}
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface ITextFieldProps extends BaseFieldProps<string> {
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
settings: PanelSettings;
|
||||
action?: CustomScript;
|
||||
actions?: CustomScript[];
|
||||
onChange: (txtValue: string) => void;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
name,
|
||||
settings,
|
||||
onChange,
|
||||
action,
|
||||
actions,
|
||||
required
|
||||
}: React.PropsWithChildren<ITextFieldProps>) => {
|
||||
const [, setRequiredFields] = useRecoilState(RequiredFieldsAtom);
|
||||
@@ -145,7 +145,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, name, action, loading]);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, name, actions, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (text !== value && (lastUpdated === null || Date.now() - DEBOUNCE_TIME > lastUpdated)) {
|
||||
@@ -168,7 +168,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
icon={<PencilIcon />}
|
||||
required={required}
|
||||
isDisabled={!!loading}
|
||||
customAction={action}
|
||||
customActions={actions}
|
||||
triggerLoading={(message) => setLoading(message)}
|
||||
onChange={onTextChange}
|
||||
/>
|
||||
|
||||
@@ -216,7 +216,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
value={(fieldValue as string) || null}
|
||||
required={!!field.required}
|
||||
settings={settings}
|
||||
action={field.action}
|
||||
actions={field.actions}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
);
|
||||
@@ -252,6 +252,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
multiple={field.multiple}
|
||||
blockData={blockData}
|
||||
onChange={onFieldChange}
|
||||
actions={field.actions}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
);
|
||||
@@ -308,7 +309,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
limit={field.taxonomyLimit}
|
||||
renderAsString={field.singleValueAsString}
|
||||
required={!!field.required}
|
||||
action={field.action}
|
||||
actions={field.actions}
|
||||
/>
|
||||
</FieldBoundary>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user