mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 01:13:08 +02:00
Add content type create, update, setting
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.)
|
||||
|
||||
+20
-2
@@ -1207,10 +1207,20 @@
|
||||
"category": "Front matter"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.generate.contenttype",
|
||||
"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",
|
||||
@@ -1688,7 +1698,15 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.generate.contenttype",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -55,5 +55,7 @@ export const COMMAND_NAME = {
|
||||
options: getCommandName("markup.options"),
|
||||
|
||||
// Content types
|
||||
generateContentType: getCommandName("generate.contenttype"),
|
||||
generateContentType: getCommandName("contenttype.generate"),
|
||||
addMissingFields: getCommandName("contenttype.addMissingFields"),
|
||||
setContentType: getCommandName("contenttype.setContentType"),
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
+15
-1
@@ -158,6 +158,14 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
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) => {
|
||||
@@ -197,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);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
+103
-4
@@ -10,6 +10,7 @@ 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 {
|
||||
|
||||
@@ -96,8 +97,13 @@ export class ContentType {
|
||||
* Generate a content type
|
||||
*/
|
||||
public static async generate() {
|
||||
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;
|
||||
@@ -108,10 +114,12 @@ export class ContentType {
|
||||
ignoreFocusOut: true,
|
||||
title: "Override default content type"
|
||||
});
|
||||
const overrideBool = override === "Yes";
|
||||
|
||||
let contentTypeName: string | undefined = `default`;
|
||||
|
||||
if (override === "No") {
|
||||
// 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",
|
||||
@@ -137,16 +145,43 @@ export class ContentType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (override === "Yes") {
|
||||
if (overrideBool) {
|
||||
const index = contentTypes.findIndex(ct => ct.name === contentTypeName);
|
||||
contentTypes[index].fields = fields;
|
||||
} else {
|
||||
@@ -156,11 +191,71 @@ export class ContentType {
|
||||
Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
|
||||
|
||||
const configPath = Settings.projectConfigPath;
|
||||
const notificationAction = await Notifications.info(`Content type ${contentTypeName} has been ${override === "Yes" ? `updated` : `generated`}.`, configPath && existsSync(configPath) ? `Open settings` : undefined);
|
||||
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() {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,6 +268,10 @@ export class ContentType {
|
||||
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({
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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 generate or update the content type for this page?</p>
|
||||
|
||||
<div className='hint__buttons'>
|
||||
<VSCodeButton appearance={`secondary`} onClick={generateContentType}>Generate 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,10 @@ 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';
|
||||
|
||||
export interface IMetadata {
|
||||
[prop: string]: string[] | string | null | IMetadata;
|
||||
@@ -80,6 +78,10 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
|
||||
|
||||
return (
|
||||
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
|
||||
<ContentTypeValidator
|
||||
fields={contentType?.fields || []}
|
||||
metadata={metadata} />
|
||||
|
||||
{
|
||||
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-editorInlayHint-foreground);
|
||||
}
|
||||
|
||||
/* File field */
|
||||
.metadata_field__file__button.not_empty {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user