diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f191af..4ae2a7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - [#101](https://github.com/estruyf/vscode-front-matter/issues/101): Date picker available on the metadata section - [#102](https://github.com/estruyf/vscode-front-matter/issues/102): Support comma separated arrays in front matter - [#103](https://github.com/estruyf/vscode-front-matter/issues/103): Added title and description field to the metadata section +- [#104](https://github.com/estruyf/vscode-front-matter/issues/104): Allow to set images in front matter from the metadata panel section +- [#105](https://github.com/estruyf/vscode-front-matter/issues/105): Content Type support with backwards compatibility ## [3.1.0] - 2021-09-10 diff --git a/docs/content/docs/commands.md b/docs/content/docs/commands.md index 6ffa7d8f..7c2e9678 100644 --- a/docs/content/docs/commands.md +++ b/docs/content/docs/commands.md @@ -109,12 +109,6 @@ Another setting is to allow you to sync the filename with the generated slug. Th ID: `frontMatter.generateSlug` -### Set current date - -Sets/updates the current date in your Markdown file. - -ID: `frontMatter.setDate` - ### Set lastmod date Sets/updates the current modified date in your Markdown file. @@ -125,4 +119,12 @@ ID: `frontMatter.setLastModifiedDate` Open the site preview of your article in VS Code. -ID: `frontMatter.preview` \ No newline at end of file +ID: `frontMatter.preview` + +## Removed commands + +### Set current date + +This command has been removed, as it became obsolete since the introduction of Content Types. + +ID: `frontMatter.setDate` \ No newline at end of file diff --git a/package.json b/package.json index a89f1744..388ba53b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "onCommand:frontMatter.createCategory", "onCommand:frontMatter.exportTaxonomy", "onCommand:frontMatter.remap", - "onCommand:frontMatter.setDate", "onCommand:frontMatter.setLastModifiedDate", "onCommand:frontMatter.generateSlug", "onCommand:frontMatter.createFromTemplate", @@ -181,18 +180,6 @@ "markdownDescription": "Specify the path you want to add after the host and before your slug. This can be used for instance to include the year/month like: `yyyy/MM`. The date will be generated based on the article its date field value. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.preview.pathname)", "scope": "Site preview" }, - "frontMatter.taxonomy.dateField": { - "type": "string", - "default": "date", - "markdownDescription": "Specifies the date field name to use in your Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.datefield)", - "scope": "Taxonomy" - }, - "frontMatter.taxonomy.modifiedField": { - "type": "string", - "default": "lastmod", - "markdownDescription": "Specifies the modified date field name to use in your Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.modifiedfield)", - "scope": "Taxonomy" - }, "frontMatter.taxonomy.tags": { "type": "array", "markdownDescription": "Specifies the tags which can be used in the Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.tags)", @@ -331,6 +318,10 @@ "name": { "type": "string", "description": "Name of the field to use" + }, + "title": { + "type": "string", + "description": "Title to show in the UI" } }, "additionalProperties": false, @@ -346,30 +337,37 @@ "name": "default", "fields": [ { + "title": "Title", "name": "title", "type": "string" }, { + "title": "Description", "name": "description", "type": "string" }, { + "title": "Publishing date", "name": "date", "type": "datetime" }, { + "title": "Article preview", "name": "preview", "type": "image" }, { + "title": "Is in draft", "name": "draft", "type": "boolean" }, { + "title": "Tags", "name": "tags", "type": "tags" }, { + "title": "Categories", "name": "categories", "type": "categories" } @@ -411,11 +409,6 @@ "title": "Remap or remove tag/category in all articles", "category": "Front matter" }, - { - "command": "frontMatter.setDate", - "title": "Set current date", - "category": "Front matter" - }, { "command": "frontMatter.setLastModifiedDate", "title": "Set lastmod date", diff --git a/src/commands/Article.ts b/src/commands/Article.ts index fd6a548a..f915ca1e 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -1,7 +1,7 @@ import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX } from './../constants/settings'; import * as vscode from 'vscode'; import { TaxonomyType } from "../models"; -import { CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_DATE_FIELD } from "../constants/settings"; +import { CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX } from "../constants/settings"; import { format } from "date-fns"; import { ArticleHelper, SettingsHelper, SlugHelper } from '../helpers'; import matter = require('gray-matter'); @@ -101,13 +101,7 @@ export class Article { * @param article */ public static updateDate(article: matter.GrayMatterFile, forceCreate: boolean = false) { - const config = SettingsHelper.getConfig(); - const dateField = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate; - const modField = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.PublishingDate; - - article = this.articleDate(article, dateField, forceCreate); - article = this.articleDate(article, modField, false); - + article.data = ArticleHelper.updateDates(article.data); return article; } diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 97b9e2cf..0c135529 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -165,7 +165,7 @@ export class Dashboard { if (msg.data?.file && msg.data?.image) { await commands.executeCommand(`workbench.view.extension.frontmatter-explorer`); await EditorHelper.showFile(msg.data.file); - ExplorerView.getInstance(extensionUri).updateMetadata({field: `preview`, value: msg.data.image}); + ExplorerView.getInstance(extensionUri).updateMetadata({field: msg.data.fieldName, value: msg.data.image}); } break; } diff --git a/src/constants/ContentType.ts b/src/constants/ContentType.ts new file mode 100644 index 00000000..6041395e --- /dev/null +++ b/src/constants/ContentType.ts @@ -0,0 +1,44 @@ +import { ContentType } from './../models/PanelSettings'; + +export const DEFAULT_CONTENT_TYPE_NAME = 'default'; + +export const DEFAULT_CONTENT_TYPE: ContentType = { + "name": "default", + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime" + }, + { + "title": "Article preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "boolean" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + } + ] +}; \ No newline at end of file diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 21a6e1ba..e5b5d0ef 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -18,7 +18,6 @@ export const COMMAND_NAME = { createCategory: getCommandName("createCategory"), exportTaxonomy: getCommandName("exportTaxonomy"), remap: getCommandName("remap"), - setDate: getCommandName("setDate"), setLastModifiedDate: getCommandName("setLastModifiedDate"), generateSlug: getCommandName("generateSlug"), createFromTemplate: getCommandName("createFromTemplate"), diff --git a/src/constants/settings.ts b/src/constants/settings.ts index cd396681..6dfce6c5 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -5,9 +5,10 @@ export const CONFIG_KEY = "frontMatter"; export const SETTING_TAXONOMY_TAGS = "taxonomy.tags"; export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories"; export const SETTING_DATE_FORMAT = "taxonomy.dateFormat"; +export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields"; +export const SETTING_TAXONOMY_CONTENT_TYPES = "taxonomy.contentTypes"; export const SETTING_DATE_FIELD = "taxonomy.dateField"; export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField"; -export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields"; export const SETTING_SLUG_PREFIX = "taxonomy.slugPrefix"; export const SETTING_SLUG_SUFFIX = "taxonomy.slugSuffix"; @@ -43,4 +44,4 @@ export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; /** * @deprecated */ -export const SETTINGS_CONTENT_FOLDERS = "content.folders"; \ No newline at end of file +export const SETTINGS_CONTENT_FOLDERS = "content.folders"; diff --git a/src/extension.ts b/src/extension.ts index c8e7d93b..d76a2da4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -78,8 +78,6 @@ export async function activate(context: vscode.ExtensionContext) { let remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap); - let setDate = vscode.commands.registerCommand(COMMAND_NAME.setDate, Article.setDate); - let setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate); let generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, Article.generateSlug); @@ -170,7 +168,6 @@ export async function activate(context: vscode.ExtensionContext) { createCategory, exportTaxonomy, remap, - setDate, setLastModifiedDate, generateSlug, createFromTemplate, diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index cdcdd25e..7c974423 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -1,12 +1,15 @@ +import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType'; +import { ContentType } from './../models/PanelSettings'; import * as vscode from 'vscode'; import * as matter from "gray-matter"; import * as fs from "fs"; -import { DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES } from '../constants'; +import { DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES } from '../constants'; import { DumpOptions } from 'js-yaml'; import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; import { SettingsHelper } from '.'; import { parse } from 'date-fns'; import { Notifications } from './Notifications'; +import { Article } from '../commands'; export class ArticleHelper { @@ -127,6 +130,42 @@ export class ArticleHelper { return; } + /** + * Retrieve the content type of the current file + * @param updatedMetadata + */ + public static getContentType(metadata: { [field: string]: string; }): ContentType { + const config = SettingsHelper.getConfig(); + const contentTypes = config.get(SETTING_TAXONOMY_CONTENT_TYPES); + + if (!contentTypes || !metadata) { + return DEFAULT_CONTENT_TYPE; + } + + let contentType = contentTypes.find(ct => ct.name === (metadata.type || DEFAULT_CONTENT_TYPE_NAME)); + if (!contentType) { + contentType = contentTypes.find(ct => ct.name === DEFAULT_CONTENT_TYPE_NAME); + } + return contentType || DEFAULT_CONTENT_TYPE; + } + + /** + * Update all dates in the metadata + * @param metadata + */ + public static updateDates(metadata: { [field: string]: string; }) { + const contentType = ArticleHelper.getContentType(metadata); + const dateFields = contentType.fields.filter((field) => field.type === "datetime"); + + for (const dateField of dateFields) { + if (typeof metadata[dateField.name] !== "undefined") { + metadata[dateField.name] = Article.formatDate(new Date()); + } + } + + return metadata; + } + /** * Parse a markdown file and its front matter * @param fileContents diff --git a/src/helpers/Extension.ts b/src/helpers/Extension.ts index 2236dad1..b0d5daa0 100644 --- a/src/helpers/Extension.ts +++ b/src/helpers/Extension.ts @@ -1,8 +1,10 @@ import { basename } from "path"; import { extensions, Uri, ExtensionContext } from "vscode"; import { Folders, WORKSPACE_PLACEHOLDER } from "../commands/Folders"; -import { SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS } from "../constants"; +import { SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType"; import { EXTENSION_BETA_ID, EXTENSION_ID, EXTENSION_STATE_VERSION } from "../constants/Extension"; +import { ContentType } from "../models"; import { Notifications } from "./Notifications"; import { SettingsHelper } from "./SettingsHelper"; @@ -65,6 +67,8 @@ export class Extension { */ public async migrateSettings(): Promise { const config = SettingsHelper.getConfig(); + + // Migration to version 3.1.0 const folders = config.get(SETTINGS_CONTENT_FOLDERS); if (folders && folders.length > 0) { const workspace = Folders.getWorkspaceFolder(); @@ -77,6 +81,44 @@ export class Extension { await config.update(`${SETTINGS_CONTENT_PAGE_FOLDERS}`, paths); } + + // Migration to version 3.2.0 + const dateField = config.get(SETTING_DATE_FIELD); + const lastModField = config.get(SETTING_MODIFIED_FIELD); + const description = config.get(SETTING_SEO_DESCRIPTION_FIELD); + const contentTypes = config.get(SETTING_TAXONOMY_CONTENT_TYPES); + + if (contentTypes) { + let defaultContentType = contentTypes.find(ct => ct.name === DEFAULT_CONTENT_TYPE_NAME); + + if (defaultContentType) { + if (dateField && dateField !== "date") { + defaultContentType.fields = defaultContentType.fields.filter(f => f.name !== "date"); + defaultContentType.fields.push({ + name: dateField, + type: "datetime" + }); + } + + if (lastModField && lastModField !== "lastmod") { + defaultContentType.fields = defaultContentType.fields.filter(f => f.name !== "lastmod"); + defaultContentType.fields.push({ + name: lastModField, + type: "datetime" + }); + } + + if (description && description !== "description") { + defaultContentType.fields = defaultContentType.fields.filter(f => f.name !== "lastmod"); + defaultContentType.fields.push({ + name: description, + type: "string" + }); + } + + await config.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes); + } + } } public async setState(propKey: string, propValue: string): Promise { diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 7d53b5bc..4a132ab4 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -13,12 +13,22 @@ export interface PanelSettings { writingSettingsEnabled: boolean; fmHighlighting: boolean; preview: PreviewSettings; + contentTypes: ContentType[]; +} + +export interface ContentType { + name: string; + fields: Field[]; +} + +export interface Field { + title?: string; + name: string; + type: "string" | "datetime" | "boolean" | "image" | "tags" | "categories"; } export interface DateInfo { format: string; - pubDate: string; - modDate: string; } export interface SEO { diff --git a/src/pagesView/components/Media/Item.tsx b/src/pagesView/components/Media/Item.tsx index f78d35fa..71368e3b 100644 --- a/src/pagesView/components/Media/Item.tsx +++ b/src/pagesView/components/Media/Item.tsx @@ -57,7 +57,8 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi const relPath = getRelPath(); Messenger.send(DashboardMessage.insertPreviewImage, { image: parseWinPath(relPath) || "", - file: viewData?.data?.filePath + file: viewData?.data?.filePath, + fieldName: viewData?.data?.fieldName }); }; diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 17744abb..99be607a 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -22,7 +22,7 @@ export const ViewPanel: React.FunctionComponent = (props: React ); } - if (!metadata || Object.keys(metadata).length === 0) { + if (!metadata || Object.keys(metadata || {}).length === 0) { return ( ); diff --git a/src/viewpanel/components/Actions.tsx b/src/viewpanel/components/Actions.tsx index 07e86888..1d0d511e 100644 --- a/src/viewpanel/components/Actions.tsx +++ b/src/viewpanel/components/Actions.tsx @@ -2,9 +2,7 @@ import * as React from 'react'; import { PanelSettings } from '../../models/PanelSettings'; import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; -import { DateAction } from './DateAction'; import { Preview } from './Preview'; -import { PublishAction } from './PublishAction'; import { SlugAction } from './SlugAction'; export interface IActionsProps { @@ -27,10 +25,6 @@ export const Actions: React.FunctionComponent = (props: React.Pro { settings?.preview?.host && } - - - { metadata && typeof metadata.draft !== undefined && } - { (settings && settings.scripts && settings.scripts.length > 0) && ( settings.scripts.map((value) => ( diff --git a/src/viewpanel/components/DateAction.tsx b/src/viewpanel/components/DateAction.tsx deleted file mode 100644 index adecf71b..00000000 --- a/src/viewpanel/components/DateAction.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import { CommandToCode } from '../CommandToCode'; -import { MessageHelper } from '../../helpers/MessageHelper'; -import { ActionButton } from './ActionButton'; - -export interface IDateActionProps {} - -export const DateAction: React.FunctionComponent = (props: React.PropsWithChildren) => { - - const setDate = () => { - MessageHelper.sendMessage(CommandToCode.updateDate); - }; - - const setLastMod = () => { - MessageHelper.sendMessage(CommandToCode.updateLastMod); - }; - - return ( - <> - - - - ); -}; \ No newline at end of file diff --git a/src/viewpanel/components/Fields/PreviewImageField.tsx b/src/viewpanel/components/Fields/PreviewImageField.tsx index 3e1f54b2..b70f472f 100644 --- a/src/viewpanel/components/Fields/PreviewImageField.tsx +++ b/src/viewpanel/components/Fields/PreviewImageField.tsx @@ -1,21 +1,24 @@ import { PhotographIcon } from '@heroicons/react/outline'; import * as React from 'react'; import { MessageHelper } from '../../../helpers/MessageHelper'; -import { PanelSettings } from '../../../models'; import { CommandToCode } from '../../CommandToCode'; import { VsLabel } from '../VscodeComponents'; export interface IPreviewImageFieldProps { label: string; + fieldName: string; value: string | null; filePath: string | null; onChange: (value: string | null) => void; } -export const PreviewImageField: React.FunctionComponent = ({label, onChange, value, filePath}: React.PropsWithChildren) => { +export const PreviewImageField: React.FunctionComponent = ({label, fieldName, onChange, value, filePath}: React.PropsWithChildren) => { const selectImage = () => { - MessageHelper.sendMessage(CommandToCode.selectImage, { filePath }); + MessageHelper.sendMessage(CommandToCode.selectImage, { + filePath, + fieldName + }); }; return ( diff --git a/src/viewpanel/components/Metadata.tsx b/src/viewpanel/components/Metadata.tsx index 9ebea453..38b22875 100644 --- a/src/viewpanel/components/Metadata.tsx +++ b/src/viewpanel/components/Metadata.tsx @@ -1,22 +1,21 @@ import * as React from 'react'; -import { PanelSettings } from '../../models'; +import { Field, PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../../helpers/MessageHelper'; import { TagType } from '../TagType'; import { Collapsible } from './Collapsible'; import { Toggle } from './Fields/Toggle'; -import { ListUnorderedIcon } from './Icons/ListUnorderedIcon'; -import { RocketIcon } from './Icons/RocketIcon'; import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon'; import { TagIcon } from './Icons/TagIcon'; import { TagPicker } from './TagPicker'; import { parseJSON } from 'date-fns'; import { DateTimeField } from './Fields/DateTimeField'; import { TextField } from './Fields/TextField'; -import { DefaultFields } from '../../constants'; import "react-datepicker/dist/react-datepicker.css"; import { PreviewImageField } from './Fields/PreviewImageField'; +import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from '../../constants/ContentType'; +import { ListUnorderedIcon } from './Icons/ListUnorderedIcon'; export interface IMetadataProps { settings: PanelSettings | undefined; metadata: { [prop: string]: string[] | string | null }; @@ -44,60 +43,110 @@ export const Metadata: React.FunctionComponent = ({settings, met return date; } - let publishing: Date | null = null; - let modifying: Date | null = null; - - if (settings?.date) { - const { modDate, pubDate } = settings.date; - publishing = metadata[pubDate] ? getDate(metadata[pubDate] as string) : null; - modifying = metadata[modDate] ? getDate(metadata[modDate] as string) : null; + if (!settings) { + return null; } - const descriptionField = settings?.seo.descriptionField || DefaultFields.Description; + const contentTypeName = metadata.type as string || DEFAULT_CONTENT_TYPE_NAME; + let contentType = settings.contentTypes.find(ct => ct.name === contentTypeName); + + if (!contentType) { + contentType = settings.contentTypes.find(ct => ct.name === DEFAULT_CONTENT_TYPE_NAME); + } + + if (!contentType || !contentType.fields) { + contentType = DEFAULT_CONTENT_TYPE; + } + + const renderFields = (ctFields: Field[]) => { + if (!ctFields) { + return; + } + + return ctFields.map(field => { + if (field.type === 'datetime') { + const dateValue = metadata[field.name] ? getDate(metadata[field.name] as string) : null; + + return ( + sendUpdate(field.name, date))} /> + ); + } else if (field.type === 'boolean') { + return ( + sendUpdate(field.name, checked)} /> + ); + } else if (field.type === 'string') { + const textValue = metadata[field.name]; + + let limit = -1; + if (field.name === 'title') { + limit = settings?.seo.title; + } else if (field.name === settings.seo.descriptionField) { + limit = settings?.seo.description; + } + + return ( + sendUpdate(field.name, value)} + value={textValue as string || null} /> + ); + } else if (field.type === 'image') { + return ( + sendUpdate(field.name, value))} /> + ); + } else if (field.type === 'tags') { + return ( + } + crntSelected={metadata[field.name] as string[] || []} + options={settings?.tags || []} + freeform={settings.freeform} + focussed={focusElm === TagType.tags} + unsetFocus={unsetFocus} /> + ); + } else if (field.type === 'categories') { + return ( + } + crntSelected={metadata.categories as string[] || []} + options={settings.categories} + freeform={settings.freeform} + focussed={focusElm === TagType.categories} + unsetFocus={unsetFocus} /> + ); + } + }); + }; return ( - sendUpdate('title', value)} - value={metadata.title as string || null} /> - - sendUpdate(descriptionField, value)} - value={metadata[descriptionField] as string || null} /> - - sendUpdate(settings?.date?.pubDate, date))} /> - { - modifying && ( - sendUpdate(settings?.date?.modDate, date))} /> - ) + renderFields(contentType?.fields) } - sendUpdate("draft", !checked)} /> - - sendUpdate('preview', value))} /> - { } @@ -108,29 +157,6 @@ export const Metadata: React.FunctionComponent = ({settings, met unsetFocus={unsetFocus} disableConfigurable /> } - - { - (settings) && ( - } - crntSelected={metadata.tags as string[] || []} - options={settings?.tags || []} - freeform={settings.freeform} - focussed={focusElm === TagType.tags} - unsetFocus={unsetFocus} /> - ) - } - { - (settings && settings.categories && settings.categories.length > 0) && ( - } - crntSelected={metadata.categories as string[] || []} - options={settings.categories} - freeform={settings.freeform} - focussed={focusElm === TagType.categories} - unsetFocus={unsetFocus} /> - ) - } ); }; \ No newline at end of file diff --git a/src/webview/ExplorerView.ts b/src/webview/ExplorerView.ts index b202a199..f94f046d 100644 --- a/src/webview/ExplorerView.ts +++ b/src/webview/ExplorerView.ts @@ -1,6 +1,6 @@ import { DashboardData } from './../models/DashboardData'; import { Template } from './../commands/Template'; -import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_COMMA_SEPARATED_FIELDS, SETTINGS_CONTENT_STATIC_FOLDERS } from './../constants/settings'; +import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_COMMA_SEPARATED_FIELDS, SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_TAXONOMY_CONTENT_TYPES } from './../constants/settings'; import * as os from 'os'; import { PanelSettings, CustomScript } from './../models/PanelSettings'; import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; @@ -95,9 +95,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { case CommandToCode.updateSlug: Article.generateSlug(); break; - case CommandToCode.updateDate: - Article.setDate(); - break; case CommandToCode.updateLastMod: Article.setLastModifiedDate(); break; @@ -221,6 +218,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const config = SettingsHelper.getConfig(); const commaSeparated = config.get(SETTING_COMMA_SEPARATED_FIELDS); const staticFolder = config.get(SETTINGS_CONTENT_STATIC_FOLDERS); + const contentTypes = config.get(SETTING_TAXONOMY_CONTENT_TYPES); const articleDetails = this.getArticleDetails(); @@ -237,27 +235,38 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } - if (updatedMetadata.preview && wsFolder) { - const staticPath = join(wsFolder.fsPath, staticFolder || "", updatedMetadata.preview); - const contentFolderPath = filePath ? join(dirname(filePath), updatedMetadata.preview) : null; + const keys = Object.keys(updatedMetadata); + if (keys.length > 0) { + updatedMetadata.filePath = filePath; + } - let previewUri = null; - if (existsSync(staticPath)) { - previewUri = Uri.file(staticPath); - } else if (contentFolderPath && existsSync(contentFolderPath)) { - previewUri = Uri.file(contentFolderPath); - } + if (keys.length > 0 && contentTypes && wsFolder) { + // 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) { + const staticPath = join(wsFolder.fsPath, staticFolder || "", updatedMetadata[field.name]); + const contentFolderPath = filePath ? join(dirname(filePath), updatedMetadata[field.name]) : null; - if (previewUri) { - const preview = this.panel?.webview.asWebviewUri(previewUri); - updatedMetadata.preview = preview?.toString() || ""; - } else { - updatedMetadata.preview = ""; + let previewUri = null; + if (existsSync(staticPath)) { + previewUri = Uri.file(staticPath); + } else if (contentFolderPath && existsSync(contentFolderPath)) { + previewUri = Uri.file(contentFolderPath); + } + + if (previewUri) { + const preview = this.panel?.webview.asWebviewUri(previewUri); + updatedMetadata[field.name]= preview?.toString() || ""; + } else { + updatedMetadata[field.name] = ""; + } + } } } this.postWebviewMessage({ command: Command.metadata, data: { - filePath, ...updatedMetadata }}); } @@ -285,10 +294,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * Update the metadata of the article */ public async updateMetadata({field, value}: { field: string, value: string }) { - const config = SettingsHelper.getConfig(); - const pubDate = config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate; - const modDate = config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified; - if (!field) { return; } @@ -303,11 +308,17 @@ export class ExplorerView implements WebviewViewProvider, Disposable { return; } - if ((field === pubDate || field === modDate) && value) { - article.data[field] = Article.formatDate(new Date(value)); - } else { - article.data[field] = value; + const contentType = ArticleHelper.getContentType(article.data); + const dateFields = contentType.fields.filter((field) => field.type === "datetime"); + + for (const dateField of dateFields) { + if ((field === dateField.name) && value) { + article.data[field] = Article.formatDate(new Date(value)); + } else { + article.data[field] = value; + } } + ArticleHelper.update(editor, article); this.pushMetadata(article.data); } @@ -375,9 +386,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { updateFileName: !!config.get(SETTING_SLUG_UPDATE_FILE_NAME), }, date: { - format: config.get(SETTING_DATE_FORMAT), - pubDate: config.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate, - modDate: config.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified + format: config.get(SETTING_DATE_FORMAT) }, tags: config.get(SETTING_TAXONOMY_TAGS) || [], categories: config.get(SETTING_TAXONOMY_CATEGORIES) || [], @@ -389,6 +398,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { fmHighlighting: config.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT), preview: Preview.getSettings(), commaSeparatedFields: config.get(SETTING_COMMA_SEPARATED_FIELDS) || [], + contentTypes: config.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], } as PanelSettings }); }