From b03d972d3146faa74e40993ce127871cfcdcd272 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 28 Jun 2024 09:23:26 +0200 Subject: [PATCH] #824 - Field actions --- CHANGELOG.md | 1 + l10n/bundle.l10n.json | 3 + package.json | 334 ++++++++---------- src/helpers/CustomScript.ts | 20 +- src/listeners/panel/ScriptListener.ts | 29 +- src/localization/index.ts | 1 + src/localization/localization.enum.ts | 8 + src/localization/localize.ts | 5 + src/models/PanelSettings.ts | 3 + src/panelWebView/CommandToCode.ts | 3 +- .../components/Fields/FieldCustomAction.tsx | 48 +++ .../components/Fields/FieldTitle.tsx | 24 +- .../components/Fields/TagPicker.tsx | 64 ++-- .../components/Fields/TextField.tsx | 40 ++- .../components/Fields/WrapperField.tsx | 2 + 15 files changed, 347 insertions(+), 238 deletions(-) create mode 100644 src/localization/localize.ts create mode 100644 src/panelWebView/components/Fields/FieldCustomAction.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e64abb..2374915d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### ✨ New features - [#823](https://github.com/estruyf/vscode-front-matter/issues/823): Integrated GitHub Copilot support for titles, descriptions, and tags +- [#824](https://github.com/estruyf/vscode-front-matter/issues/824): Added the ability to link custom actions to fields ### 🎨 Enhancements diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 99b6dfc2..bedf64e4 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -438,6 +438,9 @@ "panel.fields.wrapperField.unknown": "Unkown field type: {0}", + "panel.fields.fieldCustomAction.button.title": "Custom action", + "panel.fields.fieldCustomAction.executing": "Executing field action...", + "panel.actions.title": "Actions", "panel.articleDetails.title": "More details", diff --git a/package.json b/package.json index 5ed9b50d..fcd7a316 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "color": "#0e131f", "theme": "dark" }, - "badges": [ - { + "badges": [{ "description": "version", "url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square", "href": "https://github.com/estruyf/vscode-front-matter" @@ -71,8 +70,7 @@ "**/.frontmatter/config/*.json": "jsonc" } }, - "keybindings": [ - { + "keybindings": [{ "command": "frontMatter.dashboard", "key": "alt+d" }, @@ -96,23 +94,19 @@ } ], "viewsContainers": { - "activitybar": [ - { - "id": "frontmatter-explorer", - "title": "FM", - "icon": "$(fm-logo)" - } - ] + "activitybar": [{ + "id": "frontmatter-explorer", + "title": "FM", + "icon": "$(fm-logo)" + }] }, "views": { - "frontmatter-explorer": [ - { - "id": "frontMatter.explorer", - "name": "Front Matter", - "icon": "$(fm-logo)", - "type": "webview" - } - ] + "frontmatter-explorer": [{ + "id": "frontMatter.explorer", + "name": "Front Matter", + "icon": "$(fm-logo)", + "type": "webview" + }] }, "configuration": { "title": "%settings.configuration.title%", @@ -180,8 +174,7 @@ "frontMatter.content.defaultFileType": { "type": "string", "default": "md", - "oneOf": [ - { + "oneOf": [{ "enum": [ "md", "mdx" @@ -197,8 +190,7 @@ "frontMatter.content.defaultSorting": { "type": "string", "default": "", - "oneOf": [ - { + "oneOf": [{ "enum": [ "LastModifiedAsc", "LastModifiedDesc", @@ -550,8 +542,7 @@ "categories" ], "markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%", - "items": [ - { + "items": [{ "type": "string", "enum": [ "contentFolders", @@ -577,6 +568,7 @@ "default": [], "markdownDescription": "%setting.frontMatter.custom.scripts.markdownDescription%", "items": { + "$id": "#customscript", "type": "object", "properties": { "id": { @@ -624,8 +616,7 @@ "command": { "$id": "#scriptCommand", "type": "string", - "anyOf": [ - { + "anyOf": [{ "enum": [ "node", "bash", @@ -821,8 +812,7 @@ "title", "file" ], - "anyOf": [ - { + "anyOf": [{ "required": [ "schema" ] @@ -876,8 +866,7 @@ "id", "path" ], - "anyOf": [ - { + "anyOf": [{ "required": [ "schema" ] @@ -1118,29 +1107,26 @@ } } }, - "default": [ - { - "name": "default", - "fileTypes": null, - "fields": [ - { - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Caption", - "name": "caption", - "type": "string" - }, - { - "title": "Alt text", - "name": "alt", - "type": "string" - } - ] - } - ], + "default": [{ + "name": "default", + "fileTypes": null, + "fields": [{ + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Caption", + "name": "caption", + "type": "string" + }, + { + "title": "Alt text", + "name": "alt", + "type": "string" + } + ] + }], "scope": "Media" }, "frontMatter.media.supportedMimeTypes": { @@ -1376,8 +1362,7 @@ "default": "", "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%", "not": { - "anyOf": [ - { + "anyOf": [{ "const": "" }, { @@ -1564,6 +1549,9 @@ "description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.when.properties.caseSensitive.description%" } } + }, + "action": { + "$ref": "#customscript" } }, "additionalProperties": false, @@ -1571,8 +1559,7 @@ "type", "name" ], - "allOf": [ - { + "allOf": [{ "if": { "properties": { "type": { @@ -1784,51 +1771,48 @@ "fields" ] }, - "default": [ - { - "name": "default", - "pageBundle": false, - "fields": [ - { - "title": "Title", - "name": "title", - "type": "string" - }, - { - "title": "Description", - "name": "description", - "type": "string" - }, - { - "title": "Publishing date", - "name": "date", - "type": "datetime", - "default": "{{now}}", - "isPublishDate": true - }, - { - "title": "Content preview", - "name": "preview", - "type": "image" - }, - { - "title": "Is in draft", - "name": "draft", - "type": "boolean" - }, - { - "title": "Tags", - "name": "tags", - "type": "tags" - }, - { - "title": "Categories", - "name": "categories", - "type": "categories" - } - ] - } - ], + "default": [{ + "name": "default", + "pageBundle": false, + "fields": [{ + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime", + "default": "{{now}}", + "isPublishDate": true + }, + { + "title": "Content preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "boolean" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + } + ] + }], "scope": "Taxonomy" }, "frontMatter.taxonomy.customTaxonomy": { @@ -1841,8 +1825,7 @@ "type": "string", "description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%", "not": { - "anyOf": [ - { + "anyOf": [{ "const": "" }, { @@ -2042,8 +2025,7 @@ } } }, - "commands": [ - { + "commands": [{ "command": "frontMatter.project.switch", "title": "%command.frontMatter.project.switch%", "category": "Front Matter", @@ -2375,21 +2357,16 @@ } } ], - "submenus": [ - { - "id": "frontmatter.submenu", - "label": "Front Matter" - } - ], + "submenus": [{ + "id": "frontmatter.submenu", + "label": "Front Matter" + }], "menus": { - "webview/context": [ - { - "command": "workbench.action.webview.openDeveloperTools", - "when": "frontMatter:isDevelopment" - } - ], - "editor/title": [ - { + "webview/context": [{ + "command": "workbench.action.webview.openDeveloperTools", + "when": "frontMatter:isDevelopment" + }], + "editor/title": [{ "command": "frontMatter.markup.heading", "group": "navigation@-133", "when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg" @@ -2475,14 +2452,11 @@ "when": "resourceFilename == 'frontmatter.json'" } ], - "explorer/context": [ - { - "submenu": "frontmatter.submenu", - "group": "frontmatter@1" - } - ], - "frontmatter.submenu": [ - { + "explorer/context": [{ + "submenu": "frontmatter.submenu", + "group": "frontmatter@1" + }], + "frontmatter.submenu": [{ "command": "frontMatter.createFromTemplate", "when": "explorerResourceIsFolder", "group": "frontmatter@1" @@ -2498,8 +2472,7 @@ "group": "frontmatter@3" } ], - "commandPalette": [ - { + "commandPalette": [{ "command": "frontMatter.init", "when": "frontMatterCanInit" }, @@ -2676,8 +2649,7 @@ "when": "frontMatter:file:isValid == true" } ], - "view/title": [ - { + "view/title": [{ "command": "frontMatter.docs", "group": "navigation@-1", "when": "view == frontMatter.explorer" @@ -2714,16 +2686,13 @@ } ] }, - "languages": [ - { - "id": "frontmatter.project.output", - "mimetypes": [ - "text/x-code-output" - ] - } - ], - "grammars": [ - { + "languages": [{ + "id": "frontmatter.project.output", + "mimetypes": [ + "text/x-code-output" + ] + }], + "grammars": [{ "path": "./syntaxes/hugo.tmLanguage.json", "scopeName": "frontmatter.markdown.hugo", "injectTo": [ @@ -2736,48 +2705,45 @@ "path": "./syntaxes/frontmatter-output.tmLanguage.json" } ], - "walkthroughs": [ - { - "id": "frontmatter.welcome", - "title": "Get started with Front Matter", - "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", - "steps": [ - { - "id": "frontmatter.welcome.init", - "title": "Get started", - "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", - "media": { - "markdown": "assets/walkthrough/get-started.md" - }, - "completionEvents": [ - "onContext:frontMatterInitialized" - ] + "walkthroughs": [{ + "id": "frontmatter.welcome", + "title": "Get started with Front Matter", + "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", + "steps": [{ + "id": "frontmatter.welcome.init", + "title": "Get started", + "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", + "media": { + "markdown": "assets/walkthrough/get-started.md" }, - { - "id": "frontmatter.welcome.documentation", - "title": "Documentation", - "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", - "media": { - "markdown": "assets/walkthrough/documentation.md" - }, - "completionEvents": [ - "onLink:https://frontmatter.codes/docs" - ] + "completionEvents": [ + "onContext:frontMatterInitialized" + ] + }, + { + "id": "frontmatter.welcome.documentation", + "title": "Documentation", + "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", + "media": { + "markdown": "assets/walkthrough/documentation.md" }, - { - "id": "frontmatter.welcome.supporter", - "title": "Support the project", - "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", - "media": { - "markdown": "assets/walkthrough/support-the-project.md" - }, - "completionEvents": [ - "onLink:https://github.com/sponsors/estruyf" - ] - } - ] - } - ] + "completionEvents": [ + "onLink:https://frontmatter.codes/docs" + ] + }, + { + "id": "frontmatter.welcome.supporter", + "title": "Support the project", + "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", + "media": { + "markdown": "assets/walkthrough/support-the-project.md" + }, + "completionEvents": [ + "onLink:https://github.com/sponsors/estruyf" + ] + } + ] + }] }, "scripts": { "dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*", @@ -2905,4 +2871,4 @@ "dependencies": { "@radix-ui/react-dropdown-menu": "^2.0.6" } -} +} \ No newline at end of file diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index 9ef77e9e..8c822bb0 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -67,11 +67,11 @@ export class CustomScript { * @param path * @returns */ - private static async singleRun( + public static async singleRun( wsPath: string, script: ICustomScript, path: string | null = null - ): Promise { + ): Promise { let articlePath: string | null = path; let article: ParsedFrontMatter | null | undefined = null; @@ -99,7 +99,7 @@ export class CustomScript { articlePath as string, script ); - await CustomScript.showOutput(output, script, articlePath); + return await CustomScript.showOutput(output, script, articlePath); } ); } else { @@ -133,7 +133,7 @@ export class CustomScript { title: l10n.t(LocalizationKey.helpersCustomScriptExecuting, script.title), cancellable: false }, - async (progress, token) => { + async (_, __) => { for await (const folder of folders) { if (folder.lastModified.length > 0) { for await (const file of folder.lastModified) { @@ -266,15 +266,21 @@ export class CustomScript { output: string | null, script: ICustomScript, articlePath?: string | null - ): Promise { + ): Promise { if (output) { try { const data: { frontmatter?: { [key: string]: any }; - fmAction?: 'open' | 'copyMediaMetadata' | 'copyMediaMetadataAndDelete' | 'deleteMedia'; + fmAction?: + | 'open' + | 'copyMediaMetadata' + | 'copyMediaMetadataAndDelete' + | 'deleteMedia' + | 'fieldAction'; fmPath?: string; fmSourcePath?: string; fmDestinationPath?: string; + fmFieldValue?: any; } = JSON.parse(output); if (data.frontmatter) { @@ -326,6 +332,8 @@ export class CustomScript { await MediaHelpers.deleteFile(data.fmSourcePath); } else if (data.fmAction === 'deleteMedia' && data.fmPath) { await MediaHelpers.deleteFile(data.fmPath); + } else if (data.fmAction === 'fieldAction') { + return data.fmFieldValue || undefined; } } else { Logger.error(`No frontmatter found.`); diff --git a/src/listeners/panel/ScriptListener.ts b/src/listeners/panel/ScriptListener.ts index e80c47c8..5c2e77b0 100644 --- a/src/listeners/panel/ScriptListener.ts +++ b/src/listeners/panel/ScriptListener.ts @@ -1,5 +1,6 @@ +import { Folders } from '../../commands'; import { SETTING_CUSTOM_SCRIPTS } from '../../constants'; -import { CustomScript, Settings } from '../../helpers'; +import { CustomScript, Notifications, Settings } from '../../helpers'; import { CustomScript as ICustomScript, PostMessageData } from '../../models'; import { CommandToCode } from '../../panelWebView/CommandToCode'; import { BaseListener } from './BaseListener'; @@ -16,6 +17,32 @@ export class ScriptListener extends BaseListener { case CommandToCode.runCustomScript: this.runCustomScript(msg); break; + case CommandToCode.runFieldAction: + this.runFieldAction(msg); + break; + } + } + + private static async runFieldAction({ command, payload, requestId }: PostMessageData) { + if (!payload || !requestId || !command) { + return; + } + + const script = payload as ICustomScript; + if (script.script) { + const wsFolder = Folders.getWorkspaceFolder(); + if (!wsFolder) { + return; + } + + const fieldValue = await CustomScript.singleRun(wsFolder.fsPath, script); + + if (fieldValue) { + this.sendRequest(command, requestId, fieldValue); + } else { + Notifications.error('The script did not return a field value'); + this.sendRequestError(command, requestId, 'The script did not return a field value'); + } } } diff --git a/src/localization/index.ts b/src/localization/index.ts index f831950d..835beb6e 100644 --- a/src/localization/index.ts +++ b/src/localization/index.ts @@ -1 +1,2 @@ export * from './localization.enum'; +export * from './localize'; diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 871f0dc6..331ec3aa 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -1416,6 +1416,14 @@ export enum LocalizationKey { * Unkown field type: {0} */ panelFieldsWrapperFieldUnknown = 'panel.fields.wrapperField.unknown', + /** + * Custom action + */ + panelFieldsFieldCustomActionButtonTitle = 'panel.fields.fieldCustomAction.button.title', + /** + * Executing field action... + */ + panelFieldsFieldCustomActionExecuting = 'panel.fields.fieldCustomAction.executing', /** * Actions */ diff --git a/src/localization/localize.ts b/src/localization/localize.ts new file mode 100644 index 00000000..9f991921 --- /dev/null +++ b/src/localization/localize.ts @@ -0,0 +1,5 @@ +import * as l10n from '@vscode/l10n'; + +export const localize = (key: string, ...args: any[]): string => { + return l10n.t(key, ...args); +}; diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 5a64c84a..b13b517e 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -141,6 +141,9 @@ export interface Field { // When clause when?: WhenClause; + + // Custom action + action?: CustomScript; } export interface NumberOptions { diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts index c7e73ac5..999699be 100644 --- a/src/panelWebView/CommandToCode.ts +++ b/src/panelWebView/CommandToCode.ts @@ -46,5 +46,6 @@ export enum CommandToCode { copilotSuggestTaxonomy = 'copilot-suggest-taxonomy', searchByType = 'search-by-type', processMediaData = 'process-media-data', - isServerStarted = 'is-server-started' + isServerStarted = 'is-server-started', + runFieldAction = 'run-field-action' } diff --git a/src/panelWebView/components/Fields/FieldCustomAction.tsx b/src/panelWebView/components/Fields/FieldCustomAction.tsx new file mode 100644 index 00000000..11967380 --- /dev/null +++ b/src/panelWebView/components/Fields/FieldCustomAction.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { CustomScript } from '../../../models'; +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { CodeBracketIcon } from '@heroicons/react/24/outline'; +import { CommandToCode } from '../../CommandToCode'; +import { LocalizationKey, localize } from '../../../localization'; + +export interface IFieldCustomActionProps { + action: CustomScript; + disabled?: boolean; + triggerLoading?: (message?: string) => void; + onChange: (value: any) => void; +} + +export const FieldCustomAction: React.FunctionComponent = ({ action, disabled, triggerLoading, onChange }: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/FieldTitle.tsx b/src/panelWebView/components/Fields/FieldTitle.tsx index a2a71cb9..06fc2925 100644 --- a/src/panelWebView/components/Fields/FieldTitle.tsx +++ b/src/panelWebView/components/Fields/FieldTitle.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { useMemo } from 'react'; import { RequiredAsterix } from './RequiredAsterix'; +import { CustomScript } from '../../../models'; +import { FieldCustomAction } from './FieldCustomAction'; export interface IFieldTitleProps { label: string | JSX.Element; @@ -8,6 +10,10 @@ export interface IFieldTitleProps { className?: string; required?: boolean; actionElement?: JSX.Element; + customAction?: CustomScript; + isDisabled?: boolean; + triggerLoading?: (message?: string) => void; + onChange?: (value: any) => void; } export const FieldTitle: React.FunctionComponent = ({ @@ -16,6 +22,10 @@ export const FieldTitle: React.FunctionComponent = ({ className, required, actionElement, + customAction, + isDisabled, + triggerLoading, + onChange, }: React.PropsWithChildren) => { const Icon = useMemo(() => { return icon ? React.cloneElement(icon, { style: { width: '16px', height: '16px' } }) : null; @@ -29,7 +39,19 @@ export const FieldTitle: React.FunctionComponent = ({ - {actionElement} +
+ { + customAction && onChange && ( + + ) + } + + {actionElement} +
); }; diff --git a/src/panelWebView/components/Fields/TagPicker.tsx b/src/panelWebView/components/Fields/TagPicker.tsx index f170f310..df56ac18 100644 --- a/src/panelWebView/components/Fields/TagPicker.tsx +++ b/src/panelWebView/components/Fields/TagPicker.tsx @@ -5,7 +5,7 @@ import { CommandToCode } from '../../CommandToCode'; import { TagType } from '../../TagType'; import Downshift from 'downshift'; import { AddIcon } from '../Icons/AddIcon'; -import { BlockFieldData, CustomTaxonomyData } from '../../../models'; +import { BlockFieldData, CustomScript, CustomTaxonomyData } from '../../../models'; import { useCallback, useEffect, useMemo } from 'react'; import { messageHandler, Messenger } from '@estruyf/vscode/dist/client'; import { FieldMessage } from '../Fields/FieldMessage'; @@ -13,8 +13,7 @@ import { FieldTitle } from '../Fields/FieldTitle'; import { useRecoilValue } from 'recoil'; import { PanelSettingsAtom } from '../../state'; import { SparklesIcon } from '@heroicons/react/24/outline'; -import * as l10n from '@vscode/l10n'; -import { LocalizationKey } from '../../../localization'; +import { LocalizationKey, localize } from '../../../localization'; import useDropdownStyle from '../../hooks/useDropdownStyle'; import { CopilotIcon } from '../Icons'; @@ -37,6 +36,7 @@ export interface ITagPickerProps { limit?: number; required?: boolean; renderAsString?: boolean; + action?: CustomScript; } const TagPicker: React.FunctionComponent = ({ @@ -56,7 +56,8 @@ const TagPicker: React.FunctionComponent = ({ blockData, limit, required, - renderAsString + renderAsString, + action }: React.PropsWithChildren) => { const [selected, setSelected] = React.useState([]); const [inputValue, setInputValue] = React.useState(''); @@ -65,7 +66,7 @@ const TagPicker: React.FunctionComponent = ({ const { getDropdownStyle } = useDropdownStyle(inputRef as any); const dsRef = React.useRef | null>(null); const settings = useRecoilValue(PanelSettingsAtom); - const [loading, setLoading] = React.useState(false); + const [loading, setLoading] = React.useState(undefined); /** * Removes an option @@ -249,25 +250,29 @@ const TagPicker: React.FunctionComponent = ({ [options, inputRef, selected, freeform] ); + const updateTaxonomy = (values: string[]) => { + if (values && values instanceof Array && values.length > 0) { + const uniqValues = Array.from(new Set([...selected, ...values])); + setSelected(uniqValues); + sendUpdate(uniqValues); + setInputValue(''); + } + } + const suggestTaxonomy = useCallback( (aiType: 'ai' | 'copilot', type: TagType) => { - setLoading(true); + setLoading(localize(LocalizationKey.panelTagPickerAiGenerating)); const command = aiType === 'ai' ? CommandToCode.aiSuggestTaxonomy : CommandToCode.copilotSuggestTaxonomy; messageHandler .request(command, type) .then((values) => { - setLoading(false); - if (values && values instanceof Array && values.length > 0) { - const uniqValues = Array.from(new Set([...selected, ...values])); - setSelected(uniqValues); - sendUpdate(uniqValues); - setInputValue(''); - } + setLoading(undefined); + updateTaxonomy(values) }) .catch(() => { - setLoading(false); + setLoading(undefined); }); }, [selected] @@ -286,13 +291,13 @@ const TagPicker: React.FunctionComponent = ({ const inputPlaceholder = useMemo((): string => { if (checkIsDisabled()) { - return l10n.t( + return localize( LocalizationKey.panelTagPickerInputPlaceholderDisabled, `${limit} ${label || type.toLowerCase()}` ); } - return l10n.t(LocalizationKey.panelTagPickerInputPlaceholderEmpty, label || type.toLowerCase()); + return localize(LocalizationKey.panelTagPickerInputPlaceholderEmpty, label || type.toLowerCase()); }, [label, type, checkIsDisabled]); const showRequiredState = useMemo(() => { @@ -305,17 +310,17 @@ const TagPicker: React.FunctionComponent = ({ } return ( -
+ <> {settings?.aiEnabled && ( @@ -324,18 +329,18 @@ const TagPicker: React.FunctionComponent = ({ {settings?.copilotEnabled && ( )} -
+ ); }, [settings?.aiEnabled, settings?.copilotEnabled, label, type]); @@ -376,7 +381,7 @@ const TagPicker: React.FunctionComponent = ({ <> {` `} - ({l10n.t(LocalizationKey.panelTagPickerLimit, limit)}) + ({localize(LocalizationKey.panelTagPickerLimit, limit)}) ) : ( @@ -387,12 +392,16 @@ const TagPicker: React.FunctionComponent = ({ actionElement={actionElement} icon={icon} required={required} + isDisabled={!!loading} + customAction={action} + triggerLoading={(message) => setLoading(message)} + onChange={updateTaxonomy} />
{loading && (
- {l10n.t(LocalizationKey.panelTagPickerAiGenerating)} + {loading}
)} @@ -418,9 +427,8 @@ const TagPicker: React.FunctionComponent = ({ <>
= ({ {freeform && ( @@ -133,17 +135,17 @@ export const TextField: React.FunctionComponent = ({ {settings?.copilotEnabled && ( )} -
+ ); - }, [settings?.aiEnabled, name]); + }, [settings?.aiEnabled, settings?.copilotEnabled, name, action, loading]); useEffect(() => { if (text !== value && (lastUpdated === null || Date.now() - DEBOUNCE_TIME > lastUpdated)) { @@ -165,18 +167,22 @@ export const TextField: React.FunctionComponent = ({ actionElement={actionElement} icon={} required={required} + isDisabled={!!loading} + customAction={action} + triggerLoading={(message) => setLoading(message)} + onChange={onTextChange} />
{loading && (
- {l10n.t(LocalizationKey.panelFieldsTextFieldAiGenerate)} + {loading}
)} {wysiwyg ? ( {l10n.t(LocalizationKey.panelFieldsTextFieldLoading)}
} + fallback={
{localize(LocalizationKey.panelFieldsTextFieldLoading)}
} > @@ -206,7 +212,7 @@ export const TextField: React.FunctionComponent = ({ {limit && limit > 0 && (text || '').length > limit && (
- {l10n.t(LocalizationKey.panelFieldsTextFieldLimit, `${(text || '').length}/${limit}`)} + {localize(LocalizationKey.panelFieldsTextFieldLimit, `${(text || '').length}/${limit}`)}
)} diff --git a/src/panelWebView/components/Fields/WrapperField.tsx b/src/panelWebView/components/Fields/WrapperField.tsx index 52125286..35b45109 100644 --- a/src/panelWebView/components/Fields/WrapperField.tsx +++ b/src/panelWebView/components/Fields/WrapperField.tsx @@ -216,6 +216,7 @@ export const WrapperField: React.FunctionComponent = ({ value={(fieldValue as string) || null} required={!!field.required} settings={settings} + action={field.action} /> ); @@ -307,6 +308,7 @@ export const WrapperField: React.FunctionComponent = ({ limit={field.taxonomyLimit} renderAsString={field.singleValueAsString} required={!!field.required} + action={field.action} /> );