mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 09:23:00 +02:00
#197 - Support for multi-dimensional content type fields
This commit is contained in:
@@ -314,7 +314,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
/**
|
||||
* Update the metadata of the article
|
||||
*/
|
||||
public async updateMetadata({field, value }: { field: string, value: any, fieldData?: { multiple: boolean, value: string[] } }) {
|
||||
public async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
@@ -333,12 +333,18 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
const dateFields = contentType.fields.filter((field) => field.type === "datetime");
|
||||
const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple);
|
||||
|
||||
// Support multi-level fields
|
||||
let parentObj = article.data;
|
||||
for (const parent of parents || []) {
|
||||
parentObj = parentObj[parent];
|
||||
}
|
||||
|
||||
for (const dateField of dateFields) {
|
||||
if ((field === dateField.name) && value) {
|
||||
article.data[field] = Article.formatDate(new Date(value));
|
||||
parentObj[field] = Article.formatDate(new Date(value));
|
||||
} else if (!imageFields.find(f => f.name === field)) {
|
||||
// Only override the field data if it is not an multiselect image field
|
||||
article.data[field] = value;
|
||||
parentObj[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,15 +352,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
if (field === imageField.name) {
|
||||
// If value is an array, it means it comes from the explorer view itself (deletion)
|
||||
if (Array.isArray(value)) {
|
||||
article.data[field] = value || [];
|
||||
parentObj[field] = value || [];
|
||||
} else { // Otherwise it is coming from the media dashboard (addition)
|
||||
let fieldValue = article.data[field];
|
||||
let fieldValue = parentObj[field];
|
||||
if (fieldValue && !Array.isArray(fieldValue)) {
|
||||
fieldValue = [fieldValue];
|
||||
}
|
||||
const crntData = Object.assign([], fieldValue);
|
||||
const allRelPaths = [...(crntData || []), value];
|
||||
article.data[field] = [...new Set(allRelPaths)].filter(f => f);
|
||||
parentObj[field] = [...new Set(allRelPaths)].filter(f => f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-10
@@ -1,7 +1,7 @@
|
||||
import { PagesListener } from './../listeners/PagesListener';
|
||||
import { ArticleHelper, Settings } from ".";
|
||||
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
|
||||
import { ContentType as IContentType, DraftField } from '../models';
|
||||
import { ContentType as IContentType, DraftField, Field } from '../models';
|
||||
import { Uri, workspace, window, commands } from 'vscode';
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Questions } from "./Questions";
|
||||
@@ -109,15 +109,7 @@ export class ContentType {
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any = {};
|
||||
|
||||
for (const field of contentType.fields) {
|
||||
if (field.name === "title") {
|
||||
data[field.name] = titleValue;
|
||||
} else {
|
||||
data[field.name] = null;
|
||||
}
|
||||
}
|
||||
let data: any = this.processFields(contentType, titleValue, {});
|
||||
|
||||
data = ArticleHelper.updateDates(Object.assign({}, data));
|
||||
|
||||
@@ -136,4 +128,27 @@ export class ContentType {
|
||||
// Trigger a refresh for the dashboard
|
||||
PagesListener.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all content type fields
|
||||
* @param contentType
|
||||
* @param data
|
||||
*/
|
||||
private static processFields(obj: IContentType | Field, titleValue: string, data: any) {
|
||||
if (obj.fields) {
|
||||
for (const field of obj.fields) {
|
||||
if (field.name === "title") {
|
||||
data[field.name] = titleValue;
|
||||
} else {
|
||||
if (field.type === "object") {
|
||||
data[field.name] = this.processFields(field, titleValue, {});
|
||||
} else {
|
||||
data[field.name] = field.default || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,15 @@ export interface ContentType {
|
||||
export interface Field {
|
||||
title?: string;
|
||||
name: string;
|
||||
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy";
|
||||
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "object";
|
||||
choices?: string[] | Choice[];
|
||||
single?: boolean;
|
||||
multiple?: boolean;
|
||||
isPreviewImage?: boolean;
|
||||
hidden?: boolean;
|
||||
taxonomyId?: string;
|
||||
default?: string;
|
||||
fields?: Field[];
|
||||
}
|
||||
|
||||
export interface DateInfo {
|
||||
|
||||
@@ -19,10 +19,15 @@ 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 { CogIcon } from '@heroicons/react/outline';
|
||||
|
||||
export interface IMetadata {
|
||||
[prop: string]: string[] | string | null | IMetadata;
|
||||
}
|
||||
export interface IMetadataProps {
|
||||
settings: PanelSettings | undefined;
|
||||
metadata: { [prop: string]: string[] | string | null };
|
||||
metadata: IMetadata;
|
||||
focusElm: TagType | null;
|
||||
unsetFocus: () => void;
|
||||
}
|
||||
@@ -30,13 +35,14 @@ export interface IMetadataProps {
|
||||
const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata, focusElm, unsetFocus}: React.PropsWithChildren<IMetadataProps>) => {
|
||||
const contentType = useContentType(settings, metadata);
|
||||
|
||||
const sendUpdate = (field: string | undefined, value: any) => {
|
||||
const sendUpdate = (field: string | undefined, value: any, parents: string[]) => {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHelper.sendMessage(CommandToCode.updateMetadata, {
|
||||
field,
|
||||
parents,
|
||||
value
|
||||
});
|
||||
};
|
||||
@@ -50,7 +56,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderFields = (ctFields: Field[]) => {
|
||||
const renderFields = (ctFields: Field[], parent: IMetadata, parentFields: string[] = []) => {
|
||||
if (!ctFields) {
|
||||
return;
|
||||
}
|
||||
@@ -61,7 +67,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
}
|
||||
|
||||
if (field.type === 'datetime') {
|
||||
const dateValue = metadata[field.name] ? getDate(metadata[field.name] as string) : null;
|
||||
const dateValue = parent[field.name] ? getDate(parent[field.name] as string) : null;
|
||||
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
@@ -69,7 +75,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
label={field.title || field.name}
|
||||
date={dateValue}
|
||||
format={settings?.date?.format}
|
||||
onChange={(date => sendUpdate(field.name, date))} />
|
||||
onChange={(date => sendUpdate(field.name, date, parentFields))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'boolean') {
|
||||
@@ -78,12 +84,12 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
<Toggle
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
checked={!!metadata[field.name] as any}
|
||||
onChanged={(checked) => sendUpdate(field.name, checked)} />
|
||||
checked={!!parent[field.name] as any}
|
||||
onChanged={(checked) => sendUpdate(field.name, checked, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'string') {
|
||||
const textValue = metadata[field.name];
|
||||
const textValue = parent[field.name];
|
||||
|
||||
let limit = -1;
|
||||
if (field.name === 'title') {
|
||||
@@ -99,12 +105,12 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
singleLine={field.single}
|
||||
limit={limit}
|
||||
rows={3}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
onChange={(value) => sendUpdate(field.name, value, parentFields)}
|
||||
value={textValue as string || null} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'number') {
|
||||
const fieldValue = metadata[field.name];
|
||||
const fieldValue = parent[field.name];
|
||||
let nrValue: number | null = parseInt(fieldValue as string);
|
||||
if (isNaN(nrValue)) {
|
||||
nrValue = null;
|
||||
@@ -115,7 +121,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
<NumberField
|
||||
key={field.name}
|
||||
label={field.title || field.name}
|
||||
onChange={(value) => sendUpdate(field.name, value)}
|
||||
onChange={(value) => sendUpdate(field.name, value, parentFields)}
|
||||
value={nrValue} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
@@ -125,15 +131,15 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
<PreviewImageField
|
||||
label={field.title || field.name}
|
||||
fieldName={field.name}
|
||||
filePath={metadata.filePath as string}
|
||||
value={metadata[field.name] as PreviewImageValue | PreviewImageValue[] | null}
|
||||
filePath={parent.filePath as string}
|
||||
value={parent[field.name] as PreviewImageValue | PreviewImageValue[] | null}
|
||||
multiple={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
onChange={(value => sendUpdate(field.name, value, parentFields))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'choice') {
|
||||
const choices = field.choices || [];
|
||||
const choiceValue = metadata[field.name];
|
||||
const choiceValue = parent[field.name];
|
||||
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
@@ -142,7 +148,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
selected={choiceValue as string}
|
||||
choices={choices}
|
||||
multiSelect={field.multiple}
|
||||
onChange={(value => sendUpdate(field.name, value))} />
|
||||
onChange={(value => sendUpdate(field.name, value, parentFields))} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'tags') {
|
||||
@@ -152,7 +158,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
type={TagType.tags}
|
||||
label={field.title || field.name}
|
||||
icon={<TagIcon />}
|
||||
crntSelected={metadata[field.name] as string[] || []}
|
||||
crntSelected={parent[field.name] as string[] || []}
|
||||
options={settings?.tags || []}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.tags}
|
||||
@@ -161,7 +167,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
);
|
||||
} else if (field.type === 'taxonomy') {
|
||||
const taxonomyData = settings.customTaxonomy.find(ct => ct.id === field.taxonomyId);
|
||||
const selectedValues = metadata[field.name] || [];
|
||||
const selectedValues = parent[field.name] || [];
|
||||
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
@@ -185,7 +191,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
type={TagType.categories}
|
||||
label={field.title || field.name}
|
||||
icon={<ListUnorderedIcon />}
|
||||
crntSelected={metadata.categories as string[] || []}
|
||||
crntSelected={parent.categories as string[] || []}
|
||||
options={settings.categories}
|
||||
freeform={settings.freeform}
|
||||
focussed={focusElm === TagType.categories}
|
||||
@@ -194,7 +200,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
);
|
||||
} else if (field.type === 'draft') {
|
||||
const draftField = settings?.draftField;
|
||||
const value = metadata[field.name];
|
||||
const value = parent[field.name];
|
||||
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
@@ -203,9 +209,28 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
type={draftField.type}
|
||||
choices={draftField.choices || []}
|
||||
value={value as boolean | string | null | undefined}
|
||||
onChanged={(value: boolean | string) => sendUpdate(field.name, value)} />
|
||||
onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} />
|
||||
</FieldBoundary>
|
||||
);
|
||||
} else if (field.type === 'object') {
|
||||
if (field.fields && parent && parent[field.name]) {
|
||||
const subMetadata = parent[field.name] as IMetadata;
|
||||
return (
|
||||
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
|
||||
<div className={`metadata_field__box`}>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label metadata_field__label_parent`}>
|
||||
<span style={{ lineHeight: "16px"}}>{field.title || field.name}</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
{ renderFields(field.fields, subMetadata, [...parentFields, field.name]) }
|
||||
</div>
|
||||
</FieldBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -216,7 +241,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
|
||||
|
||||
{
|
||||
renderFields(contentType?.fields)
|
||||
renderFields(contentType?.fields, metadata)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user