#197 - Update fields type + support for taxonomy and images

This commit is contained in:
Elio Struyf
2022-01-12 11:25:44 +01:00
parent 9a91be8025
commit dee30923ff
12 changed files with 126 additions and 53 deletions

View File

@@ -4,6 +4,7 @@
### 🎨 Enhancements
- Added default field value for content type fields
- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing.
## [5.10.0] - 2022-01-10

View File

@@ -659,6 +659,14 @@ input:checked + .field__toggle__slider:before {
margin-top: .5rem;
}
.vscode-light .metadata_field__preview_image__preview {
background: rgba(0, 0, 0, 0.1);
}
.vscode-dark .metadata_field__preview_image__preview {
background: rgba(255, 255, 255, 0.1);
}
.metadata_field__preview_image__preview {
background-color: var(--vscode-button-secondaryBackground);
display: flex;

View File

@@ -437,7 +437,7 @@
"tags",
"categories",
"draft",
"object"
"fields"
],
"description": "Define the type of field"
},
@@ -541,7 +541,7 @@
"if": {
"properties": {
"type": {
"const": "object"
"const": "fields"
}
}
},

View File

@@ -77,6 +77,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
image: parseWinPath(relPath) || "",
file: viewData?.data?.filePath,
fieldName: viewData?.data?.fieldName,
parents: viewData?.data?.parents,
multiple: viewData?.data?.multiple,
value: viewData?.data?.value,
position: viewData?.data?.position || null,
@@ -193,6 +194,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const extension = fileInfo?.pop();
const name = fileInfo?.join('.');
console.log(viewData?.data)
return (
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50">
@@ -220,7 +223,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
viewData?.data?.filePath ? (
<>
<QuickAction
title='Insert image with markdown markup'
title={(viewData.data.metadataInsert && viewData.data.fieldName) ? `Insert image for your "${viewData.data.fieldName}" field` : `Insert image with markdown markup`}
onClick={insertToArticle}>
<PlusIcon className={`h-5 w-5`} aria-hidden="true" />
</QuickAction>

View File

@@ -9,7 +9,7 @@ import { Command } from "../panelWebView/Command";
import { CommandToCode } from '../panelWebView/CommandToCode';
import { Article } from '../commands';
import { TagType } from '../panelWebView/TagType';
import { CustomTaxonomyData, DraftField, ScriptType, TaxonomyType } from '../models';
import { ContentType, CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models';
import { exec } from 'child_process';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Content } from 'mdast';
@@ -101,13 +101,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
Article.toggleDraft();
break;
case CommandToCode.updateTags:
this.updateTags(TagType.tags, msg.data || []);
this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCategories:
this.updateTags(TagType.categories, msg.data || []);
this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateKeywords:
this.updateTags(TagType.keywords, msg.data || []);
this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCustomTaxonomy:
this.updateCustomTaxonomy(msg.data);
@@ -249,32 +249,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
// Get the current content type
const contentType = ArticleHelper.getContentType(updatedMetadata);
if (contentType) {
const imageFields = contentType.fields.filter((field) => field.type === "image");
for (const field of imageFields) {
if (updatedMetadata[field.name]) {
const imageData = ImageHelper.allRelToAbs(field, updatedMetadata[field.name])
if (imageData) {
if (field.multiple && imageData instanceof Array) {
const preview = imageData.map(preview => preview && preview.absPath ? ({
...preview,
webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString()
}) : null);
updatedMetadata[field.name] = preview || [];
} else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) {
const preview = this.panel?.webview.asWebviewUri(imageData.absPath);
updatedMetadata[field.name] = {
...imageData,
webviewUrl: preview ? preview.toString() : null
};
}
} else {
updatedMetadata[field.name] = field.multiple ? [] : "";
}
}
}
this.processImageFields(updatedMetadata, contentType.fields)
}
}
@@ -444,6 +419,56 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
});
}
/**
* Process the image fields in the content type
* @param updatedMetadata
* @param fields
* @param parents
*/
private processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) {
const imageFields = fields.filter((field) => field.type === "image");
// Support multi-level fields
let parentObj = updatedMetadata;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
// Process image fields
for (const field of imageFields) {
if (parentObj[field.name]) {
const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name])
if (imageData) {
if (field.multiple && imageData instanceof Array) {
const preview = imageData.map(preview => preview && preview.absPath ? ({
...preview,
webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString()
}) : null);
parentObj[field.name] = preview || [];
} else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) {
const preview = this.panel?.webview.asWebviewUri(imageData.absPath);
parentObj[field.name] = {
...imageData,
webviewUrl: preview ? preview.toString() : null
};
}
} else {
parentObj[field.name] = field.multiple ? [] : "";
}
}
}
// Check if there are sub-fields to process
const subFields = fields.filter((field) => field.type === "fields");
if (subFields?.length > 0) {
for (const field of subFields) {
this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]);
}
}
}
/**
* Retrieve the file its front matter
*/
@@ -464,7 +489,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* @param tagType
* @param values
*/
private updateTags(tagType: TagType, values: string[]) {
private updateTags(tagType: TagType, values: string[], parents: string[]) {
const editor = window.activeTextEditor;
if (!editor) {
return "";
@@ -472,7 +497,14 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
article.data[tagType.toLowerCase()] = values || [];
// Support multi-level fields
let parentObj = article.data;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
parentObj[tagType.toLowerCase()] = values || [];
ArticleHelper.update(editor, article);
this.pushMetadata(article!.data);
}
@@ -494,7 +526,14 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
article.data[data.name] = data.options || [];
// Support multi-level fields
let parentObj = article.data;
for (const parent of data.parents || []) {
parentObj = parentObj[parent];
}
parentObj[data.name] = data.options || [];
ArticleHelper.update(editor, article);
this.pushMetadata(article!.data);
}

View File

@@ -140,7 +140,7 @@ export class ContentType {
if (field.name === "title") {
data[field.name] = titleValue;
} else {
if (field.type === "object") {
if (field.type === "fields") {
data[field.name] = this.processFields(field, titleValue, {});
} else {
data[field.name] = field.default || "";

View File

@@ -295,7 +295,7 @@ export class MediaHelpers {
panel.getMediaSelection();
} else {
panel.getMediaSelection();
panel.updateMetadata({field: data.fieldName, value: data.image });
panel.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents });
}
}
}

View File

@@ -4,4 +4,5 @@ export interface CustomTaxonomyData {
name: string | undefined;
options?: string[] | undefined;
option?: string | undefined;
parents?: string[];
}

View File

@@ -34,7 +34,7 @@ export interface ContentType {
export interface Field {
title?: string;
name: string;
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "object";
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields";
choices?: string[] | Choice[];
single?: boolean;
multiple?: boolean;

View File

@@ -15,18 +15,21 @@ export interface IPreviewImageFieldProps {
fieldName: string;
value: PreviewImageValue | PreviewImageValue[] | null;
filePath: string | null;
parents?: string[];
multiple?: boolean;
onChange: (value: string | string[] | null) => void;
}
export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps> = ({label, fieldName, onChange, value, filePath, multiple}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps> = ({label, fieldName, onChange, value, filePath, multiple, parents}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
const selectImage = () => {
MessageHelper.sendMessage(CommandToCode.selectImage, {
filePath: filePath,
fieldName,
value,
multiple
multiple,
metadataInsert: true,
parents
});
};
@@ -35,6 +38,8 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
onChange(newValue);
}
console.log(fieldName, value, filePath, parents)
return (
<div className={`metadata_field`}>
<VsLabel>

View File

@@ -131,10 +131,11 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
<PreviewImageField
label={field.title || field.name}
fieldName={field.name}
filePath={parent.filePath as string}
filePath={metadata.filePath as string}
parents={parentFields}
value={parent[field.name] as PreviewImageValue | PreviewImageValue[] | null}
multiple={field.multiple}
onChange={(value => sendUpdate(field.name, value, parentFields))} />
onChange={(value) => sendUpdate(field.name, value, parentFields)} />
</FieldBoundary>
);
} else if (field.type === 'choice') {
@@ -162,7 +163,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
options={settings?.tags || []}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
unsetFocus={unsetFocus}
parents={parentFields} />
</FieldBoundary>
);
} else if (field.type === 'taxonomy') {
@@ -181,7 +183,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
focussed={focusElm === TagType.custom}
unsetFocus={unsetFocus}
fieldName={field.name}
taxonomyId={field.taxonomyId} />
taxonomyId={field.taxonomyId}
parents={parentFields} />
</FieldBoundary>
);
} else if (field.type === 'categories') {
@@ -195,7 +198,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
unsetFocus={unsetFocus}
parents={parentFields} />
</FieldBoundary>
);
} else if (field.type === 'draft') {
@@ -212,7 +216,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} />
</FieldBoundary>
);
} else if (field.type === 'object') {
} else if (field.type === 'fields') {
if (field.fields && parent && parent[field.name]) {
const subMetadata = parent[field.name] as IMetadata;
return (

View File

@@ -12,19 +12,21 @@ import { CustomTaxonomyData } from '../../models';
export interface ITagPickerProps {
type: TagType;
icon: JSX.Element;
label?: string;
crntSelected: string[];
options: string[];
freeform: boolean;
focussed: boolean;
unsetFocus: () => void;
parents?: string[];
label?: string;
disableConfigurable?: boolean;
fieldName?: string;
taxonomyId?: string;
}
const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsWithChildren<ITagPickerProps>) => {
const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId } = props;
const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId, parents } = props;
const [ selected, setSelected ] = React.useState<string[]>([]);
const [ inputValue, setInputValue ] = React.useState<string>("");
const prevSelected = usePrevious(crntSelected);
@@ -65,16 +67,26 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
*/
const sendUpdate = (values: string[]) => {
if (type === TagType.tags) {
MessageHelper.sendMessage(CommandToCode.updateTags, values);
MessageHelper.sendMessage(CommandToCode.updateTags, {
values,
parents
});
} else if (type === TagType.categories) {
MessageHelper.sendMessage(CommandToCode.updateCategories, values);
MessageHelper.sendMessage(CommandToCode.updateCategories, {
values,
parents
});
} else if (type === TagType.keywords) {
MessageHelper.sendMessage(CommandToCode.updateKeywords, values);
MessageHelper.sendMessage(CommandToCode.updateKeywords, {
values,
parents
});
} else if (type === TagType.custom) {
MessageHelper.sendMessage(CommandToCode.updateCustomTaxonomy, {
id: taxonomyId,
name: fieldName,
options: values
options: values,
parents
} as CustomTaxonomyData);
}
};