#105 - Content Type support with backwards compatibility

This commit is contained in:
Elio Struyf
2021-09-16 16:20:58 +02:00
parent e159a6239c
commit 9247884a5b
19 changed files with 314 additions and 181 deletions

View File

@@ -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

View File

@@ -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`
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`

View File

@@ -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",

View File

@@ -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<string>, 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;
}

View File

@@ -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;
}

View File

@@ -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"
}
]
};

View File

@@ -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"),

View File

@@ -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";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";

View File

@@ -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,

View File

@@ -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<ContentType[]>(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

View File

@@ -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<void> {
const config = SettingsHelper.getConfig();
// Migration to version 3.1.0
const folders = config.get<any>(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<string>(SETTING_DATE_FIELD);
const lastModField = config.get<string>(SETTING_MODIFIED_FIELD);
const description = config.get<string>(SETTING_SEO_DESCRIPTION_FIELD);
const contentTypes = config.get<ContentType[]>(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<void> {

View File

@@ -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 {

View File

@@ -57,7 +57,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({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
});
};

View File

@@ -22,7 +22,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
);
}
if (!metadata || Object.keys(metadata).length === 0) {
if (!metadata || Object.keys(metadata || {}).length === 0) {
return (
<BaseView settings={settings} folderAndFiles={folderAndFiles} />
);

View File

@@ -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<IActionsProps> = (props: React.Pro
{ settings?.preview?.host && <Preview slug={metadata.slug} /> }
<DateAction />
{ metadata && typeof metadata.draft !== undefined && <PublishAction draft={metadata.draft} />}
{
(settings && settings.scripts && settings.scripts.length > 0) && (
settings.scripts.map((value) => (

View File

@@ -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<IDateActionProps> = (props: React.PropsWithChildren<IDateActionProps>) => {
const setDate = () => {
MessageHelper.sendMessage(CommandToCode.updateDate);
};
const setLastMod = () => {
MessageHelper.sendMessage(CommandToCode.updateLastMod);
};
return (
<>
<ActionButton onClick={setDate} title={`Set publish date`} />
<ActionButton onClick={setLastMod} title={`Set modified date`} />
</>
);
};

View File

@@ -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<IPreviewImageFieldProps> = ({label, onChange, value, filePath}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps> = ({label, fieldName, onChange, value, filePath}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
const selectImage = () => {
MessageHelper.sendMessage(CommandToCode.selectImage, { filePath });
MessageHelper.sendMessage(CommandToCode.selectImage, {
filePath,
fieldName
});
};
return (

View File

@@ -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<IMetadataProps> = ({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 (
<DateTimeField
key={field.name}
label={field.title || field.name}
date={dateValue}
format={settings?.date?.format}
onChange={(date => sendUpdate(field.name, date))} />
);
} else if (field.type === 'boolean') {
return (
<Toggle
key={field.name}
label={field.title || field.name}
checked={!!metadata[field.name] as any}
onChanged={(checked) => 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 (
<TextField
key={field.name}
label={field.title || field.name}
limit={limit}
rows={3}
onChange={(value) => sendUpdate(field.name, value)}
value={textValue as string || null} />
);
} else if (field.type === 'image') {
return (
<PreviewImageField
key={field.name}
label={field.title || field.name}
fieldName={field.name}
filePath={metadata.filePath as string}
value={metadata[field.name] as string}
onChange={(value => sendUpdate(field.name, value))} />
);
} else if (field.type === 'tags') {
return (
<TagPicker
key={field.name}
type={TagType.tags}
icon={<TagIcon />}
crntSelected={metadata[field.name] as string[] || []}
options={settings?.tags || []}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
);
} else if (field.type === 'categories') {
return (
<TagPicker
key={field.name}
type={TagType.categories}
icon={<ListUnorderedIcon />}
crntSelected={metadata.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
);
}
});
};
return (
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
<TextField
label={`Title`}
limit={settings?.seo.title}
onChange={(value) => sendUpdate('title', value)}
value={metadata.title as string || null} />
<TextField
label={`Description`}
limit={settings?.seo.description}
rows={3}
onChange={(value) => sendUpdate(descriptionField, value)}
value={metadata[descriptionField] as string || null} />
<DateTimeField
label={`Article date`}
date={publishing}
format={settings?.date?.format}
onChange={(date => sendUpdate(settings?.date?.pubDate, date))} />
{
modifying && (
<DateTimeField
label={`Modified date`}
date={modifying}
format={settings?.date?.format}
onChange={(date => sendUpdate(settings?.date?.modDate, date))} />
)
renderFields(contentType?.fields)
}
<Toggle
label={`Published`}
checked={!metadata.draft as any}
onChanged={(checked) => sendUpdate("draft", !checked)} />
<PreviewImageField
label={`Preview`}
filePath={metadata.filePath as string}
value={metadata.preview as string}
onChange={(value => sendUpdate('preview', value))} />
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
@@ -108,29 +157,6 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, met
unsetFocus={unsetFocus}
disableConfigurable />
}
{
(settings) && (
<TagPicker type={TagType.tags}
icon={<TagIcon />}
crntSelected={metadata.tags as string[] || []}
options={settings?.tags || []}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
)
}
{
(settings && settings.categories && settings.categories.length > 0) && (
<TagPicker type={TagType.categories}
icon={<ListUnorderedIcon />}
crntSelected={metadata.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
)
}
</Collapsible>
);
};

View File

@@ -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<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
const staticFolder = config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
const contentTypes = config.get<string>(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<boolean>(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
});
}