mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-03-28 17:42:40 +01:00
#105 - Content Type support with backwards compatibility
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
29
package.json
29
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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
44
src/constants/ContentType.ts
Normal file
44
src/constants/ContentType.ts
Normal 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"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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"),
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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} />
|
||||
);
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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`} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user