mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-05 16:33:04 +02:00
Merge branch 'poc/generate-ct' into dev
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- Updated JSON schema link to supported version by VS Code (draft-07)
|
||||
- Hide the view mode action from the Front Matter panel if no custom modes are defined
|
||||
- Fix in decode base64 uploaded video files
|
||||
- Fix for a lightbox on other types of documents (pdf, etc.)
|
||||
|
||||
@@ -355,6 +355,11 @@
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
}
|
||||
|
||||
.ext_link_block a:hover,
|
||||
.ext_link_block button:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.table__cell {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -598,6 +598,7 @@
|
||||
"panel.globalSettings",
|
||||
"panel.seo",
|
||||
"panel.actions",
|
||||
"panel.contentType",
|
||||
"panel.metadata",
|
||||
"panel.recentlyModified",
|
||||
"panel.otherActions",
|
||||
@@ -1206,6 +1207,21 @@
|
||||
"title": "Authenticate",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.generate",
|
||||
"title": "Generate content type from current file",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.addMissingFields",
|
||||
"title": "Add missing fields from front matter to content type",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.setContentType",
|
||||
"title": "Set the content type to use for the current file",
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"title": "Blockquote",
|
||||
@@ -1681,6 +1697,18 @@
|
||||
{
|
||||
"command": "frontMatter.generateSlug",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.generate",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.addMissingFields",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.contenttype.setContentType",
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
|
||||
+1
-18
@@ -30,7 +30,7 @@ export class Article {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = Article.getCurrent();
|
||||
const article = ArticleHelper.getCurrent();
|
||||
|
||||
if (!article) {
|
||||
return;
|
||||
@@ -375,23 +375,6 @@ export class Article {
|
||||
} as DashboardData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current article
|
||||
*/
|
||||
private static getCurrent(): ParsedFrontMatter | undefined {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the article date and return it
|
||||
* @param article
|
||||
|
||||
@@ -53,4 +53,9 @@ export const COMMAND_NAME = {
|
||||
orderedlist: getCommandName("markup.orderedlist"),
|
||||
taskList: getCommandName("markup.tasklist"),
|
||||
options: getCommandName("markup.options"),
|
||||
|
||||
// Content types
|
||||
generateContentType: getCommandName("contenttype.generate"),
|
||||
addMissingFields: getCommandName("contenttype.addMissingFields"),
|
||||
setContentType: getCommandName("contenttype.setContentType"),
|
||||
};
|
||||
@@ -8,6 +8,7 @@ export const FEATURE_FLAG = {
|
||||
metadata: "panel.metadata",
|
||||
recentlyModified: "panel.recentlyModified",
|
||||
otherActions: "panel.otherActions",
|
||||
contentType: "panel.contentType",
|
||||
},
|
||||
dashboard: {
|
||||
snippets: {
|
||||
|
||||
@@ -26,6 +26,11 @@ export const TelemetryEvent = {
|
||||
updateMediaMetadata: 'updateMediaMetadata',
|
||||
openExplorerView: 'openExplorerView',
|
||||
|
||||
// Content types
|
||||
generateContentType: 'generateContentType',
|
||||
addMissingFields: 'addMissingFields',
|
||||
setContentType: 'setContentType',
|
||||
|
||||
// Custom scripts
|
||||
runCustomScript: 'runCustomScript',
|
||||
runMediaScript: 'runMediaScript',
|
||||
|
||||
+19
-1
@@ -154,6 +154,18 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const createByTemplate = vscode.commands.registerCommand(COMMAND_NAME.createByTemplate, Folders.create);
|
||||
const createContent = vscode.commands.registerCommand(COMMAND_NAME.createContent, Content.create);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.generateContentType, ContentType.generate)
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.addMissingFields, ContentType.addMissingFields)
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
vscode.commands.registerCommand(COMMAND_NAME.setContentType, ContentType.setContentType)
|
||||
);
|
||||
|
||||
// Initialize command
|
||||
Template.init();
|
||||
const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
|
||||
@@ -193,7 +205,13 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => triggerShowDraftStatus(`onDidChangeActiveTextEditor`)));
|
||||
subscriptions.push(vscode.window.onDidChangeTextEditorSelection((e) => {
|
||||
if (e.kind === vscode.TextEditorSelectionChangeKind.Mouse) {
|
||||
triggerShowDraftStatus(`onDidChangeTextEditorSelection`);
|
||||
statusDebouncer(() => triggerShowDraftStatus(`onDidChangeTextEditorSelection`), 200);
|
||||
}
|
||||
}));
|
||||
subscriptions.push(vscode.workspace.onDidChangeTextDocument((TextDocumentChangeEvent) => {
|
||||
const filePath = TextDocumentChangeEvent.document.uri.fsPath;
|
||||
if (filePath && !filePath.toLowerCase().startsWith(`extension-output`)) {
|
||||
statusDebouncer(() => triggerShowDraftStatus(`onDidChangeTextEditorSelection`), 200);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -44,6 +44,23 @@ export class ArticleHelper {
|
||||
return ArticleHelper.parseFile(fileContents, document.fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current article
|
||||
*/
|
||||
public static getCurrent(): ParsedFrontMatter | undefined {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file's front matter by its path
|
||||
* @param filePath
|
||||
|
||||
+275
-3
@@ -1,15 +1,17 @@
|
||||
import { ModeListener } from './../listeners/general/ModeListener';
|
||||
import { PagesListener } from './../listeners/dashboard';
|
||||
import { ArticleHelper, Settings } from ".";
|
||||
import { SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
|
||||
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
|
||||
import { ContentType as IContentType, DraftField, Field } from '../models';
|
||||
import { Uri, commands } from 'vscode';
|
||||
import { Uri, commands, window } from 'vscode';
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Questions } from "./Questions";
|
||||
import { writeFileSync } from "fs";
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType";
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { processKnownPlaceholders } from './PlaceholderHelper';
|
||||
import { basename } from 'path';
|
||||
|
||||
export class ContentType {
|
||||
|
||||
@@ -92,6 +94,262 @@ export class ContentType {
|
||||
return Settings.get<IContentType[]>(SETTING_TAXONOMY_CONTENT_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a content type
|
||||
*/
|
||||
public static async generate() {
|
||||
if (!(await ContentType.verify())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.generateContentType);
|
||||
|
||||
const content = ArticleHelper.getCurrent();
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
const filePath = editor?.document.uri.fsPath;
|
||||
|
||||
if (!content || !content.data) {
|
||||
Notifications.warning(`No front matter data found to generate a content type.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const override = await window.showQuickPick(["Yes", "No"], {
|
||||
placeHolder: "Do you want to override the default content type?",
|
||||
ignoreFocusOut: true,
|
||||
title: "Override default content type"
|
||||
});
|
||||
const overrideBool = override === "Yes";
|
||||
|
||||
let contentTypeName: string | undefined = `default`;
|
||||
|
||||
// Ask for the new content type name
|
||||
if (!overrideBool) {
|
||||
contentTypeName = await window.showInputBox({
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: "Enter the name of the content type to generate",
|
||||
prompt: "Enter the name of the content type to generate",
|
||||
title: "Generate Content Type",
|
||||
validateInput: (value: string) => {
|
||||
if (!value) {
|
||||
return "Please enter a name for the content type";
|
||||
}
|
||||
|
||||
const contentTypes = ContentType.getAll();
|
||||
if (contentTypes && contentTypes.find(ct => ct.name.toLowerCase() === value.toLowerCase())) {
|
||||
return "A content type with this name already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (!contentTypeName) {
|
||||
Notifications.warning(`You didn't specify a name for the content type.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ask if the content type needs to be used as a page bundle
|
||||
let pageBundle = false;
|
||||
const fileName = filePath ? basename(filePath) : undefined;
|
||||
if (fileName?.startsWith(`index.`)) {
|
||||
const pageBundleAnswer = await window.showQuickPick(["Yes", "No"], {
|
||||
placeHolder: "Do you want to use this content type as a page bundle?",
|
||||
ignoreFocusOut: true,
|
||||
title: "Use as page bundle"
|
||||
});
|
||||
pageBundle = pageBundleAnswer === "Yes";
|
||||
}
|
||||
|
||||
const fields = ContentType.generateFields(content.data);
|
||||
if (!overrideBool && !fields.some(f => f.name === "type")) {
|
||||
fields.push({
|
||||
name: "type",
|
||||
type: "string",
|
||||
default: contentTypeName,
|
||||
hidden: true
|
||||
} as Field);
|
||||
}
|
||||
|
||||
// Update the type field in the page
|
||||
if (!overrideBool && editor) {
|
||||
content.data["type"] = contentTypeName;
|
||||
ArticleHelper.update(editor, content);
|
||||
}
|
||||
|
||||
const newContentType: IContentType = {
|
||||
name: contentTypeName,
|
||||
pageBundle,
|
||||
fields
|
||||
};
|
||||
|
||||
const contentTypes = ContentType.getAll() || [];
|
||||
|
||||
if (overrideBool) {
|
||||
const index = contentTypes.findIndex(ct => ct.name === contentTypeName);
|
||||
contentTypes[index].fields = fields;
|
||||
} else {
|
||||
contentTypes.push(newContentType);
|
||||
}
|
||||
|
||||
Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
|
||||
|
||||
const configPath = Settings.projectConfigPath;
|
||||
const notificationAction = await Notifications.info(`Content type ${contentTypeName} has been ${overrideBool ? `updated` : `generated`}.`, configPath && existsSync(configPath) ? `Open settings` : undefined);
|
||||
|
||||
if (notificationAction === "Open settings" && configPath && existsSync(configPath)) {
|
||||
commands.executeCommand('vscode.open', Uri.file(configPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing fields to the content type
|
||||
*/
|
||||
public static async addMissingFields() {
|
||||
if (!(await ContentType.verify())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.addMissingFields);
|
||||
|
||||
const content = ArticleHelper.getCurrent();
|
||||
|
||||
if (!content || !content.data) {
|
||||
Notifications.warning(`No front matter data found to add missing fields.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(content?.data);
|
||||
const updatedFields = ContentType.generateFields(content.data, contentType.fields);
|
||||
|
||||
const contentTypes = ContentType.getAll() || [];
|
||||
const index = contentTypes.findIndex(ct => ct.name === contentType.name);
|
||||
contentTypes[index].fields = updatedFields;
|
||||
|
||||
Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
|
||||
|
||||
const configPath = Settings.projectConfigPath;
|
||||
const notificationAction = await Notifications.info(`Content type ${contentType.name} has been updated.`, configPath && existsSync(configPath) ? `Open settings` : undefined);
|
||||
|
||||
if (notificationAction === "Open settings" && configPath && existsSync(configPath)) {
|
||||
commands.executeCommand('vscode.open', Uri.file(configPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content type to be used for the current file
|
||||
*/
|
||||
public static async setContentType() {
|
||||
if (!(await ContentType.verify())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.setContentType);
|
||||
|
||||
const content = ArticleHelper.getCurrent();
|
||||
const contentTypes = ContentType.getAll() || [];
|
||||
|
||||
if (!content || !content.data) {
|
||||
Notifications.warning(`No front matter data found to set the content type.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const ctAnswer = await window.showQuickPick(contentTypes.map(ct => ct.name), {
|
||||
title: "Select the content type",
|
||||
ignoreFocusOut: true,
|
||||
placeHolder: "Which content type would you like to use?"
|
||||
});
|
||||
|
||||
if (!ctAnswer) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.data.type = ctAnswer;
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
ArticleHelper.update(editor!, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the fields from the data
|
||||
* @param data
|
||||
* @param fields
|
||||
* @returns
|
||||
*/
|
||||
private static generateFields(data: any, fields: any[] = []) {
|
||||
for (const field in data) {
|
||||
const fieldData = data[field];
|
||||
|
||||
if (fields.some(f => f.name === field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldData && fieldData instanceof Array && fieldData.length > 0 && typeof fieldData[0] === "string") {
|
||||
if (field.toLowerCase() === "tag" || field.toLowerCase() === "tags") {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "tags",
|
||||
} as Field);
|
||||
} else if (field.toLowerCase() === "category" || field.toLowerCase() === "categories") {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "categories",
|
||||
} as Field);
|
||||
} else {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "choice",
|
||||
choices: fieldData
|
||||
} as Field);
|
||||
}
|
||||
} else if (fieldData && fieldData instanceof Array && fieldData.length > 0 && typeof fieldData[0] === "object") {
|
||||
const newFields = ContentType.generateFields(fieldData);
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "block",
|
||||
fields: newFields
|
||||
} as Field);
|
||||
} else if (fieldData && fieldData instanceof Object) {
|
||||
const newFields = ContentType.generateFields(fieldData);
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "fields",
|
||||
fields: newFields
|
||||
} as Field);
|
||||
} else {
|
||||
if (!isNaN(new Date(fieldData).getDate())) {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "datetime"
|
||||
} as Field);
|
||||
} else if (field.toLowerCase() === "draft") {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: "draft"
|
||||
} as Field);
|
||||
} else if (field.toLowerCase() === "slug") {
|
||||
// Do nothing
|
||||
} else {
|
||||
fields.push({
|
||||
title: field,
|
||||
name: field,
|
||||
type: typeof fieldData
|
||||
} as Field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file with the specified content type
|
||||
* @param contentType
|
||||
@@ -167,4 +425,18 @@ export class ContentType {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the content type feature is enabled
|
||||
* @returns
|
||||
*/
|
||||
private static async verify() {
|
||||
const hasFeature = await ModeListener.hasFeature(FEATURE_FLAG.panel.contentType);
|
||||
if (!hasFeature) {
|
||||
Notifications.warning(`The content type actions are not available in this mode.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +328,7 @@ export class Settings {
|
||||
* Get the project config path
|
||||
* @returns
|
||||
*/
|
||||
private static get projectConfigPath() {
|
||||
public static get projectConfigPath() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const fmConfig = join(wsFolder.fsPath, Settings.globalFile);
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function useContentType(settings: PanelSettings | Settings | unde
|
||||
|
||||
setContentType(ct || DEFAULT_CONTENT_TYPE)
|
||||
}
|
||||
}, [settings?.contentTypes, metadata?.data]);
|
||||
}, [settings?.contentTypes, metadata?.type]);
|
||||
|
||||
return contentType;
|
||||
}
|
||||
@@ -51,6 +51,30 @@ export class ModeListener extends BaseListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the mode has the feature enabled
|
||||
* @param feature
|
||||
* @returns
|
||||
*/
|
||||
public static async hasFeature(feature: string) {
|
||||
const modes = Settings.get<Mode[]>(SETTING_GLOBAL_MODES);
|
||||
|
||||
if (!modes || modes.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const activeMode = ModeSwitch.getMode();
|
||||
if (activeMode) {
|
||||
const mode = modes.find(m => m.id === activeMode);
|
||||
return mode?.features.find(f => f === feature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the context
|
||||
*/
|
||||
public static async resetEnablement() {
|
||||
await commands.executeCommand('setContext', CONTEXT.isSnippetsDashboardEnabled, true);
|
||||
await commands.executeCommand('setContext', CONTEXT.isDataDashboardEnabled, true);
|
||||
|
||||
@@ -43,6 +43,12 @@ export class DataListener extends BaseListener {
|
||||
case CommandToCode.updatePlaceholder:
|
||||
this.updatePlaceholder(msg?.data?.field, msg?.data?.value, msg?.data?.title);
|
||||
break;
|
||||
case CommandToCode.generateContentType:
|
||||
commands.executeCommand(COMMAND_NAME.generateContentType);
|
||||
case CommandToCode.addMissingFields:
|
||||
commands.executeCommand(COMMAND_NAME.addMissingFields);
|
||||
case CommandToCode.setContentType:
|
||||
commands.executeCommand(COMMAND_NAME.setContentType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,7 @@ export enum CommandToCode {
|
||||
getImageUrl = "get-image-url",
|
||||
updatePlaceholder = "update-placeholder",
|
||||
getMode = "get-mode",
|
||||
generateContentType = "generate-content-type",
|
||||
addMissingFields = "add-missing-fields",
|
||||
setContentType = "set-content-type",
|
||||
}
|
||||
@@ -70,7 +70,8 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
|
||||
settings={settings}
|
||||
metadata={metadata}
|
||||
focusElm={focusElm}
|
||||
unsetFocus={unsetFocus} />
|
||||
unsetFocus={unsetFocus}
|
||||
features={mode?.features || []} />
|
||||
</FeatureFlag>
|
||||
|
||||
<FeatureFlag features={mode?.features || []} flag={FEATURE_FLAG.panel.recentlyModified}>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { VSCodeButton, VSCodeDivider } from '@vscode/webview-ui-toolkit/react';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { MessageHelper } from '../../../helpers/MessageHelper';
|
||||
import { Field } from '../../../models';
|
||||
import { CommandToCode } from '../../CommandToCode';
|
||||
import { IMetadata } from '../Metadata';
|
||||
import { VsLabel } from '../VscodeComponents';
|
||||
|
||||
export interface IContentTypeValidatorProps {
|
||||
fields: Field[];
|
||||
metadata: IMetadata
|
||||
}
|
||||
|
||||
const fieldsToIgnore = [`filePath`, `articleDetails`, `slug`];
|
||||
|
||||
export const ContentTypeValidator: React.FunctionComponent<IContentTypeValidatorProps> = ({ fields, metadata}: React.PropsWithChildren<IContentTypeValidatorProps>) => {
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
const metadataFields = Object.keys(metadata).filter(key => !fieldsToIgnore.includes(key));
|
||||
|
||||
for (const mField of metadataFields) {
|
||||
if (!fields.find(field => field.name === mField)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [fields, metadata]);
|
||||
|
||||
|
||||
const generateContentType = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.generateContentType);
|
||||
};
|
||||
|
||||
const addMissingFields = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.addMissingFields);
|
||||
};
|
||||
|
||||
const setContentType = () => {
|
||||
MessageHelper.sendMessage(CommandToCode.setContentType);
|
||||
};
|
||||
|
||||
|
||||
if (isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='hint'>
|
||||
<VsLabel>
|
||||
<div className={`metadata_field__label metadata_field__alert`}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fillRule="evenodd" clipRule="evenodd" d="M7.56 1h.88l6.54 12.26-.44.74H1.44L1 13.26 7.56 1zM8 2.28L2.28 13H13.7L8 2.28zM8.625 12v-1h-1.25v1h1.25zm-1.25-2V6h1.25v4h-1.25z"/></svg>
|
||||
|
||||
<span>Content type</span>
|
||||
</div>
|
||||
</VsLabel>
|
||||
|
||||
<p className='inline_hint'>We noticed field differences between the content type and the front matter data.</p>
|
||||
|
||||
<p className='inline_hint'>Would you like to create, update, or set the content type for this content?</p>
|
||||
|
||||
<div className='hint__buttons'>
|
||||
<VSCodeButton appearance={`secondary`} onClick={generateContentType}>Create content type</VSCodeButton>
|
||||
|
||||
<VSCodeButton appearance={`secondary`} onClick={addMissingFields}>Add missing fields</VSCodeButton>
|
||||
|
||||
<VSCodeButton appearance={`secondary`} onClick={setContentType}>Set content type</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<VSCodeDivider />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -4,12 +4,12 @@ import { CommandToCode } from '../CommandToCode';
|
||||
import { MessageHelper } from '../../helpers/MessageHelper';
|
||||
import { TagType } from '../TagType';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { SymbolKeywordIcon } from './Icons/SymbolKeywordIcon';
|
||||
import { TagPicker } from './TagPicker';
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import useContentType from '../../hooks/useContentType';
|
||||
import FieldBoundary from './ErrorBoundary/FieldBoundary';
|
||||
import { WrapperField } from './Fields/WrapperField';
|
||||
import { ContentTypeValidator } from './ContentType/ContentTypeValidator';
|
||||
import { FeatureFlag } from '../../components/features/FeatureFlag';
|
||||
import { FEATURE_FLAG } from '../../constants';
|
||||
|
||||
export interface IMetadata {
|
||||
[prop: string]: string[] | string | null | IMetadata;
|
||||
@@ -18,10 +18,11 @@ export interface IMetadataProps {
|
||||
settings: PanelSettings | undefined;
|
||||
metadata: IMetadata;
|
||||
focusElm: TagType | null;
|
||||
features: string[];
|
||||
unsetFocus: () => void;
|
||||
}
|
||||
|
||||
const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata, focusElm, unsetFocus}: React.PropsWithChildren<IMetadataProps>) => {
|
||||
const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, features, metadata, focusElm, unsetFocus}: React.PropsWithChildren<IMetadataProps>) => {
|
||||
const contentType = useContentType(settings, metadata);
|
||||
|
||||
const sendUpdate = (field: string | undefined, value: any, parents: string[]) => {
|
||||
@@ -80,6 +81,12 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
|
||||
return (
|
||||
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
|
||||
<FeatureFlag features={features || []} flag={FEATURE_FLAG.panel.contentType}>
|
||||
<ContentTypeValidator
|
||||
fields={contentType?.fields || []}
|
||||
metadata={metadata} />
|
||||
</FeatureFlag>
|
||||
|
||||
{
|
||||
renderFields(contentType?.fields || [], metadata)
|
||||
}
|
||||
|
||||
@@ -253,6 +253,33 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Metadata section - Content type */
|
||||
.metadata_field__alert svg {
|
||||
color: var(--vscode-editorWarning-foreground)
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hint__buttons vscode-button {
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hint__buttons vscode-button:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
vscode-divider {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.inline_hint {
|
||||
color: var(--vscode-sideBar-foreground);
|
||||
}
|
||||
|
||||
/* File field */
|
||||
.metadata_field__file__button.not_empty {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user