diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 33f301b2..a15f05cc 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -38,6 +38,9 @@ export class DataListener extends BaseListener { case CommandToCode.frameworkCommand: this.openTerminalWithCommand(msg.data.command); break; + case CommandToCode.updatePlaceholder: + this.updatePlaceholder(msg?.data?.field, msg?.data?.value, msg?.data?.title); + break; } } @@ -271,4 +274,13 @@ export class DataListener extends BaseListener { } } } + + private static updatePlaceholder(field: string, value: string, title: string) { + if (field && value) { + value = ArticleHelper.processKnownPlaceholders(value, title || ""); + value = ArticleHelper.processCustomPlaceholders(value, title || ""); + } + + this.sendMsg(Command.updatePlaceholder, { field, value }); + } } \ No newline at end of file diff --git a/src/panelWebView/Command.ts b/src/panelWebView/Command.ts index 410a1cf4..c89e789e 100644 --- a/src/panelWebView/Command.ts +++ b/src/panelWebView/Command.ts @@ -8,4 +8,5 @@ export enum Command { folderInfo = "folderInfo", mediaSelectionData = "mediaSelectionData", sendMediaUrl = "sendMediaUrl", + updatePlaceholder = "updatePlaceholder", } \ No newline at end of file diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts index 800c4942..94ddb683 100644 --- a/src/panelWebView/CommandToCode.ts +++ b/src/panelWebView/CommandToCode.ts @@ -31,4 +31,5 @@ export enum CommandToCode { frameworkCommand = "framework-command", updateStartCommand = "update-start-command", getImageUrl = "get-image-url", + updatePlaceholder = "update-placeholder", } \ No newline at end of file diff --git a/src/panelWebView/components/DataBlock/DataBlockField.tsx b/src/panelWebView/components/DataBlock/DataBlockField.tsx index 630699d4..582900e1 100644 --- a/src/panelWebView/components/DataBlock/DataBlockField.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockField.tsx @@ -18,11 +18,11 @@ export interface IDataBlockFieldProps { fieldsRenderer: ( ctFields: Field[], parent: IMetadata, - parentFields?: string[], + parentFields: string[], blockData?: BlockFieldData, onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void, parentBlock?: string | null - ) => (JSX.Element | null)[] | undefined + ) => (JSX.Element | null)[] | undefined; onSubmit: (data: any) => void; parentBlock: string | null | undefined; } diff --git a/src/panelWebView/components/DataBlock/DataBlockRecords.tsx b/src/panelWebView/components/DataBlock/DataBlockRecords.tsx index 10195aa0..6796a8e8 100644 --- a/src/panelWebView/components/DataBlock/DataBlockRecords.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockRecords.tsx @@ -48,10 +48,6 @@ export const DataBlockRecords = ({ fieldGroups, records, selectedIndex, onSort, Records - - diff --git a/src/panelWebView/components/Fields/WrapperField.tsx b/src/panelWebView/components/Fields/WrapperField.tsx new file mode 100644 index 00000000..a1b2e622 --- /dev/null +++ b/src/panelWebView/components/Fields/WrapperField.tsx @@ -0,0 +1,337 @@ + + +import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { DateHelper } from '../../../helpers/DateHelper'; +import { MessageHelper } from '../../../helpers/MessageHelper'; +import { BlockFieldData, Field, PanelSettings } from '../../../models'; +import { Command } from '../../Command'; +import { CommandToCode } from '../../CommandToCode'; +import { TagType } from '../../TagType'; +import { DataBlockField } from '../DataBlock'; +import FieldBoundary from '../ErrorBoundary/FieldBoundary'; +import { ListUnorderedIcon } from '../Icons/ListUnorderedIcon'; +import { TagIcon } from '../Icons/TagIcon'; +import { JsonField } from '../JsonField'; +import { IMetadata } from '../Metadata'; +import { TagPicker } from '../TagPicker'; +import { VsLabel } from '../VscodeComponents'; +import { ChoiceField } from './ChoiceField'; +import { DateTimeField } from './DateTimeField'; +import { DraftField } from './DraftField'; +import { NumberField } from './NumberField'; +import { PreviewImageField, PreviewImageValue } from './PreviewImageField'; +import { TextField } from './TextField'; +import { Toggle } from './Toggle'; + +export interface IWrapperFieldProps { + field: Field; + parent: IMetadata; + parentFields: string[]; + metadata: IMetadata; + settings: PanelSettings; + blockData: BlockFieldData | undefined; + focusElm: TagType | null; + parentBlock: string | null | undefined; + onSendUpdate: (field: string | undefined, value: any, parents: string[]) => void; + unsetFocus: () => void; + renderFields: ( + ctFields: Field[], + parent: IMetadata, + parentFields: string[], + blockData?: BlockFieldData, + onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void, + parentBlock?: string | null + ) => (JSX.Element | null)[] | undefined +} + +export const WrapperField: React.FunctionComponent = ({ + field, parent, parentFields, metadata, settings, blockData, focusElm, parentBlock, onSendUpdate, unsetFocus, renderFields +}: React.PropsWithChildren) => { + const [ fieldValue, setFieldValue ] = useState(undefined); + + const listener = useCallback((event: any) => { + const message = event.data; + + if (message.command === Command.updatePlaceholder) { + const data = message.data; + if (data.field === field.name) { + setFieldValue(data.value); + onSendUpdate(field.name, data.value, parentFields); + } + } + }, [field]); + + + const getDate = (date: string | Date): Date | null => { + const parsedDate = DateHelper.tryParse(date, settings?.date?.format); + return parsedDate || date as Date | null; + } + + useEffect(() => { + let value: any = parent[field.name]; + let shouldSetField = false; + + if (field.type === 'datetime') { + value = getDate(value) || undefined; + } + + if (value === undefined && field.default) { + value = field.default; + + if (field.type === 'datetime') { + value = getDate(value) || null; + } + + onSendUpdate(field.name, value, parentFields); + } else { + if (field.type === "tags" || field.type === "categories" || field.type === "taxonomy") { + value = []; + } + + shouldSetField = true; + } + + // Check if the field value contains a placeholder + if (value && typeof value === "string" && value.includes(`{{`) && value.includes(`}}`)) { + window.addEventListener('message', listener); + MessageHelper.sendMessage(CommandToCode.updatePlaceholder, { + field: field.name, + title: metadata["title"], + value + }); + } else { + // Did not contain a placeholder, so value can be set + if (fieldValue === undefined || value !== fieldValue) { + setFieldValue(value || null); + } + } + + return () => { + window.removeEventListener('message', listener); + } + }, [field, parent]); + + if (field.hidden || fieldValue === undefined) { + return null; + } + + if (field.type === 'datetime') { + return ( + + onSendUpdate(field.name, date, parentFields))} /> + + ); + } else if (field.type === 'boolean') { + return ( + + onSendUpdate(field.name, checked, parentFields)} /> + + ); + } else if (field.type === 'string') { + + let limit = -1; + if (field.name === 'title') { + limit = settings?.seo.title; + } else if (field.name === settings.seo.descriptionField) { + limit = settings?.seo.description; + } + + return ( + + onSendUpdate(field.name, value, parentFields)} + value={fieldValue as string || null} /> + + ); + } else if (field.type === 'number') { + let nrValue: number | null = parseInt(fieldValue as string); + if (isNaN(nrValue)) { + nrValue = null; + } + + return ( + + onSendUpdate(field.name, value, parentFields)} + value={nrValue} /> + + ); + } else if (field.type === 'image') { + return ( + + onSendUpdate(field.name, value, parentFields)} /> + + ); + } else if (field.type === 'choice') { + const choices = field.choices || []; + + return ( + + onSendUpdate(field.name, value, parentFields))} /> + + ); + } else if (field.type === 'tags') { + return ( + + } + crntSelected={fieldValue as string[] || []} + options={settings?.tags || []} + freeform={settings.freeform || false} + focussed={focusElm === TagType.tags} + unsetFocus={unsetFocus} + parents={parentFields} + blockData={blockData} /> + + ); + } else if (field.type === 'taxonomy') { + const taxonomyData = settings.customTaxonomy.find(ct => ct.id === field.taxonomyId); + + return ( + + } + crntSelected={fieldValue as string[] || []} + options={taxonomyData?.options || []} + freeform={settings.freeform || false} + focussed={focusElm === TagType.custom} + unsetFocus={unsetFocus} + fieldName={field.name} + taxonomyId={field.taxonomyId} + parents={parentFields} + blockData={blockData} /> + + ); + } else if (field.type === 'categories') { + let selectedValues = parent[field.name]; + if (!selectedValues && typeof parent[field.name] === "undefined" && field.default) { + selectedValues = field.default; + onSendUpdate(field.name, selectedValues, parentFields); + } else { + selectedValues = []; + } + + return ( + + } + crntSelected={selectedValues as string[] || []} + options={settings.categories} + freeform={settings.freeform || false} + focussed={focusElm === TagType.categories} + unsetFocus={unsetFocus} + parents={parentFields} + blockData={blockData} /> + + ); + } else if (field.type === 'draft') { + const draftField = settings?.draftField; + let draftValue = parent[field.name]; + + if (!draftValue && typeof parent[field.name] === "undefined" && field.default) { + draftValue = field.default; + onSendUpdate(field.name, draftValue, parentFields); + } + + return ( + + onSendUpdate(field.name, value, parentFields)} /> + + ); + } else if (field.type === 'fields') { + if (field.fields && parent) { + if (!parent[field.name]) { + parent[field.name] = {}; + } + + const subMetadata = parent[field.name] as IMetadata; + return ( + +
+ +
+ {field.title || field.name} +
+
+ + { renderFields(field.fields, subMetadata, [...parentFields, field.name], blockData, onSendUpdate) } +
+
+ ); + } + + return null; + } else if (field.type === 'json') { + const collectionData = parent[field.name]; + + return ( + + onSendUpdate(field.name, value, parentFields)} /> + + ); + } else if (field.type === 'block') { + const blockData = Object.assign([], parent[field.name]); + + return ( + + onSendUpdate(field.name, value, parentFields)} /> + + ); + } else { + console.warn(`Unknown field type: ${field.type}`); + return null; + } +}; \ No newline at end of file diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index b654de11..f055be8c 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -4,24 +4,12 @@ import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../../helpers/MessageHelper'; import { TagType } from '../TagType'; import { Collapsible } from './Collapsible'; -import { Toggle } from './Fields/Toggle'; import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon'; -import { TagIcon } from './Icons/TagIcon'; import { TagPicker } from './TagPicker'; -import { DateTimeField } from './Fields/DateTimeField'; -import { TextField } from './Fields/TextField'; import "react-datepicker/dist/react-datepicker.css"; -import { PreviewImageField, PreviewImageValue } from './Fields/PreviewImageField'; -import { ListUnorderedIcon } from './Icons/ListUnorderedIcon'; -import { NumberField } from './Fields/NumberField'; -import { ChoiceField } from './Fields/ChoiceField'; import useContentType from '../../hooks/useContentType'; -import { DateHelper } from '../../helpers/DateHelper'; import FieldBoundary from './ErrorBoundary/FieldBoundary'; -import { DraftField } from './Fields/DraftField'; -import { VsLabel } from './VscodeComponents'; -import { DataBlockField } from './DataBlock'; -import { JsonField } from './JsonField'; +import { WrapperField } from './Fields/WrapperField'; export interface IMetadata { [prop: string]: string[] | string | null | IMetadata; @@ -48,11 +36,6 @@ const Metadata: React.FunctionComponent = ({settings, metadata, }); }; - const getDate = (date: string | Date): Date | null => { - const parsedDate = DateHelper.tryParse(date, settings?.date?.format); - return parsedDate || date as Date | null; - } - if (!settings) { return null; } @@ -64,7 +47,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, blockData?: BlockFieldData, onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void, parentBlock?: string | null - ) => { + ) : (JSX.Element | null)[] | undefined => { if (!ctFields) { return; } @@ -77,281 +60,22 @@ const Metadata: React.FunctionComponent = ({settings, metadata, } }; - return ctFields.map(field => { - if (field.hidden) { - return null; - } - - if (field.type === 'datetime') { - let dateValue = parent[field.name] ? getDate(parent[field.name] as string) : null; - if (!dateValue && typeof parent[field.name] === "undefined" && field.default) { - dateValue = getDate(field.default); - onSendUpdate(field.name, dateValue, parentFields); - } - - return ( - - onSendUpdate(field.name, date, parentFields))} /> - - ); - } else if (field.type === 'boolean') { - let toggleValue = parent[field.name]; - if (!toggleValue && typeof parent[field.name] === "undefined" && parent[field.name]) { - toggleValue = field.default as any; - onSendUpdate(field.name, toggleValue, parentFields); - } - - return ( - - onSendUpdate(field.name, checked, parentFields)} /> - - ); - } else if (field.type === 'string') { - let textValue = parent[field.name]; - - if (!textValue && typeof parent[field.name] === "undefined" && field.default) { - textValue = field.default; - onSendUpdate(field.name, textValue, parentFields); - } - - let limit = -1; - if (field.name === 'title') { - limit = settings?.seo.title; - } else if (field.name === settings.seo.descriptionField) { - limit = settings?.seo.description; - } - - return ( - - onSendUpdate(field.name, value, parentFields)} - value={textValue as string || null} /> - - ); - } else if (field.type === 'number') { - let fieldValue = parent[field.name]; - if (!fieldValue && typeof parent[field.name] === "undefined" && field.default) { - fieldValue = field.default; - onSendUpdate(field.name, fieldValue, parentFields); - } - - let nrValue: number | null = parseInt(fieldValue as string); - if (isNaN(nrValue)) { - nrValue = null; - } - - return ( - - onSendUpdate(field.name, value, parentFields)} - value={nrValue} /> - - ); - } else if (field.type === 'image') { - let imageValue = parent[field.name]; - if (!imageValue && typeof parent[field.name] === "undefined" && field.default) { - imageValue = field.default; - onSendUpdate(field.name, imageValue, parentFields); - } - - return ( - - onSendUpdate(field.name, value, parentFields)} /> - - ); - } else if (field.type === 'choice') { - const choices = field.choices || []; - let choiceValue = parent[field.name]; - - if (!choiceValue && typeof parent[field.name] === "undefined" && field.default) { - choiceValue = field.default; - onSendUpdate(field.name, choiceValue, parentFields); - } - - return ( - - onSendUpdate(field.name, value, parentFields))} /> - - ); - } else if (field.type === 'tags') { - let tagsValue = parent[field.name]; - if (!tagsValue && typeof parent[field.name] === "undefined" && field.default) { - tagsValue = field.default; - onSendUpdate(field.name, tagsValue, parentFields); - } else { - tagsValue = []; - } - - return ( - - } - crntSelected={parent[field.name] as string[] || []} - options={settings?.tags || []} - freeform={settings.freeform || false} - focussed={focusElm === TagType.tags} - unsetFocus={unsetFocus} - parents={parentFields} - blockData={blockData} /> - - ); - } else if (field.type === 'taxonomy') { - const taxonomyData = settings.customTaxonomy.find(ct => ct.id === field.taxonomyId); - let selectedValues = parent[field.name]; - if (!selectedValues && typeof parent[field.name] === "undefined" && field.default) { - selectedValues = field.default; - onSendUpdate(field.name, selectedValues, parentFields); - } else { - selectedValues = []; - } - - return ( - - } - crntSelected={selectedValues as string[] || []} - options={taxonomyData?.options || []} - freeform={settings.freeform || false} - focussed={focusElm === TagType.custom} - unsetFocus={unsetFocus} - fieldName={field.name} - taxonomyId={field.taxonomyId} - parents={parentFields} - blockData={blockData} /> - - ); - } else if (field.type === 'categories') { - let selectedValues = parent[field.name]; - if (!selectedValues && typeof parent[field.name] === "undefined" && field.default) { - selectedValues = field.default; - onSendUpdate(field.name, selectedValues, parentFields); - } else { - selectedValues = []; - } - - return ( - - } - crntSelected={selectedValues as string[] || []} - options={settings.categories} - freeform={settings.freeform || false} - focussed={focusElm === TagType.categories} - unsetFocus={unsetFocus} - parents={parentFields} - blockData={blockData} /> - - ); - } else if (field.type === 'draft') { - const draftField = settings?.draftField; - let draftValue = parent[field.name]; - - if (!draftValue && typeof parent[field.name] === "undefined" && field.default) { - draftValue = field.default; - onSendUpdate(field.name, draftValue, parentFields); - } - - return ( - - onSendUpdate(field.name, value, parentFields)} /> - - ); - } else if (field.type === 'fields') { - if (field.fields && parent) { - if (!parent[field.name]) { - parent[field.name] = {}; - } - - const subMetadata = parent[field.name] as IMetadata; - return ( - -
- -
- {field.title || field.name} -
-
- - { renderFields(field.fields, subMetadata, [...parentFields, field.name], blockData, onFieldUpdate) } -
-
- ); - } - - return null; - } else if (field.type === 'json') { - const collectionData = parent[field.name]; - - return ( - - onSendUpdate(field.name, value, parentFields)} /> - - ); - } else if (field.type === 'block') { - const blockData = Object.assign([], parent[field.name]); - - return ( - - onSendUpdate(field.name, value, parentFields)} /> - - ); - } else { - console.warn(`Unknown field type: ${field.type}`); - return null; - } - }); + return ctFields.map(field => ( + + )); }; return (