From dee30923ff423be9783af7a74416872719948aee Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Jan 2022 11:25:44 +0100 Subject: [PATCH] #197 - Update fields type + support for taxonomy and images --- CHANGELOG.md | 1 + assets/media/styles.css | 8 ++ package.json | 4 +- .../components/Media/Item.tsx | 5 +- src/explorerView/ExplorerView.ts | 105 ++++++++++++------ src/helpers/ContentType.ts | 2 +- src/helpers/MediaHelpers.ts | 2 +- src/models/CustomTaxonomyData.ts | 1 + src/models/PanelSettings.ts | 2 +- .../components/Fields/PreviewImageField.tsx | 9 +- src/panelWebView/components/Metadata.tsx | 16 ++- src/panelWebView/components/TagPicker.tsx | 24 +++- 12 files changed, 126 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04f006c..35434156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/assets/media/styles.css b/assets/media/styles.css index c193c441..518f898a 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -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; diff --git a/package.json b/package.json index 1f90d11d..72dd3416 100644 --- a/package.json +++ b/package.json @@ -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" } } }, diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index f3706cef..3e837db6 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -77,6 +77,7 @@ export const Item: React.FunctionComponent = ({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 = ({media}: React.PropsWi const extension = fileInfo?.pop(); const name = fileInfo?.join('.'); + console.log(viewData?.data) + return ( <>
  • @@ -220,7 +223,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi viewData?.data?.filePath ? ( <> diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index e278ebe6..e1d9f8f6 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -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); } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 900e7ed6..1a94ea63 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -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 || ""; diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index c0e69416..579c4326 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -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 }); } } } diff --git a/src/models/CustomTaxonomyData.ts b/src/models/CustomTaxonomyData.ts index ad0510db..f9b9f1b1 100644 --- a/src/models/CustomTaxonomyData.ts +++ b/src/models/CustomTaxonomyData.ts @@ -4,4 +4,5 @@ export interface CustomTaxonomyData { name: string | undefined; options?: string[] | undefined; option?: string | undefined; + parents?: string[]; } \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 2bcba5e8..b8edc95a 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -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; diff --git a/src/panelWebView/components/Fields/PreviewImageField.tsx b/src/panelWebView/components/Fields/PreviewImageField.tsx index c1f41899..e6b45ecb 100644 --- a/src/panelWebView/components/Fields/PreviewImageField.tsx +++ b/src/panelWebView/components/Fields/PreviewImageField.tsx @@ -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 = ({label, fieldName, onChange, value, filePath, multiple}: React.PropsWithChildren) => { +export const PreviewImageField: React.FunctionComponent = ({label, fieldName, onChange, value, filePath, multiple, parents}: React.PropsWithChildren) => { const selectImage = () => { MessageHelper.sendMessage(CommandToCode.selectImage, { filePath: filePath, fieldName, value, - multiple + multiple, + metadataInsert: true, + parents }); }; @@ -35,6 +38,8 @@ export const PreviewImageField: React.FunctionComponent onChange(newValue); } + console.log(fieldName, value, filePath, parents) + return (
    diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index 4a7c7161..8cd1bd03 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -131,10 +131,11 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, value, parentFields))} /> + onChange={(value) => sendUpdate(field.name, value, parentFields)} /> ); } else if (field.type === 'choice') { @@ -162,7 +163,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, options={settings?.tags || []} freeform={settings.freeform} focussed={focusElm === TagType.tags} - unsetFocus={unsetFocus} /> + unsetFocus={unsetFocus} + parents={parentFields} /> ); } else if (field.type === 'taxonomy') { @@ -181,7 +183,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, focussed={focusElm === TagType.custom} unsetFocus={unsetFocus} fieldName={field.name} - taxonomyId={field.taxonomyId} /> + taxonomyId={field.taxonomyId} + parents={parentFields} /> ); } else if (field.type === 'categories') { @@ -195,7 +198,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, options={settings.categories} freeform={settings.freeform} focussed={focusElm === TagType.categories} - unsetFocus={unsetFocus} /> + unsetFocus={unsetFocus} + parents={parentFields} /> ); } else if (field.type === 'draft') { @@ -212,7 +216,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} /> ); - } 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 ( diff --git a/src/panelWebView/components/TagPicker.tsx b/src/panelWebView/components/TagPicker.tsx index 8e02b71d..3ce29307 100644 --- a/src/panelWebView/components/TagPicker.tsx +++ b/src/panelWebView/components/TagPicker.tsx @@ -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 = (props: React.PropsWithChildren) => { - 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([]); const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); @@ -65,16 +67,26 @@ const TagPicker: React.FunctionComponent = (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); } };