mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
#197 - Update fields type + support for taxonomy and images
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 || "";
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export interface CustomTaxonomyData {
|
||||
name: string | undefined;
|
||||
options?: string[] | undefined;
|
||||
option?: string | undefined;
|
||||
parents?: string[];
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user