From c4055eb37cb3160168359bc51d8c84601e708ca6 Mon Sep 17 00:00:00 2001
From: Elio Struyf
Date: Thu, 21 Apr 2022 21:18:24 +0200
Subject: [PATCH 1/5] Content type generation
---
package.json | 9 +++
src/commands/Article.ts | 19 +-----
src/constants/Extension.ts | 3 +
src/extension.ts | 4 ++
src/helpers/ArticleHelper.ts | 17 +++++
src/helpers/ContentType.ts | 124 ++++++++++++++++++++++++++++++++++-
6 files changed, 157 insertions(+), 19 deletions(-)
diff --git a/package.json b/package.json
index 1b1592e9..1e76debb 100644
--- a/package.json
+++ b/package.json
@@ -1206,6 +1206,11 @@
"title": "Authenticate",
"category": "Front matter"
},
+ {
+ "command": "frontMatter.generate.contenttype",
+ "title": "Generate content type from current file",
+ "category": "Front matter"
+ },
{
"command": "frontMatter.markup.blockquote",
"title": "Blockquote",
@@ -1681,6 +1686,10 @@
{
"command": "frontMatter.generateSlug",
"when": "frontMatter:file:isValid == true"
+ },
+ {
+ "command": "frontMatter.generate.contenttype",
+ "when": "frontMatter:file:isValid == true"
}
],
"view/title": [
diff --git a/src/commands/Article.ts b/src/commands/Article.ts
index 4015443b..90f41f24 100644
--- a/src/commands/Article.ts
+++ b/src/commands/Article.ts
@@ -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
diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts
index c3331a7f..a55eb954 100644
--- a/src/constants/Extension.ts
+++ b/src/constants/Extension.ts
@@ -53,4 +53,7 @@ export const COMMAND_NAME = {
orderedlist: getCommandName("markup.orderedlist"),
taskList: getCommandName("markup.tasklist"),
options: getCommandName("markup.options"),
+
+ // Content types
+ generateContentType: getCommandName("generate.contenttype"),
};
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 25cc9ccb..b761dfba 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -154,6 +154,10 @@ 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)
+ );
+
// Initialize command
Template.init();
const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, async (cb: Function) => {
diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts
index 1418bf48..b998df62 100644
--- a/src/helpers/ArticleHelper.ts
+++ b/src/helpers/ArticleHelper.ts
@@ -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
diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts
index db032352..e089a240 100644
--- a/src/helpers/ContentType.ts
+++ b/src/helpers/ContentType.ts
@@ -2,7 +2,7 @@ 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 { 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";
@@ -92,6 +92,128 @@ export class ContentType {
return Settings.get(SETTING_TAXONOMY_CONTENT_TYPES);
}
+ /**
+ * Generate a content type
+ */
+ public static async generate() {
+ const content = ArticleHelper.getCurrent();
+
+ if (!content || !content.data) {
+ Notifications.warning(`No front matter data found to generate a content type.`);
+ return;
+ }
+
+ const answer = 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 (!answer) {
+ Notifications.warning(`You didn't specify a name for the content type.`);
+ return;
+ }
+
+ const fields = ContentType.generateFields(content.data);
+
+ const newContentType: IContentType = {
+ name: answer,
+ fields
+ };
+
+ const contentTypes = ContentType.getAll() || [];
+ contentTypes.push(newContentType);
+
+ Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
+ Notifications.info(`Content type ${answer} has been generated.`);
+ }
+
+ /**
+ * 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 (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 {
+ fields.push({
+ title: field,
+ name: field,
+ type: typeof fieldData
+ } as Field);
+ }
+ }
+ }
+
+ return fields;
+ }
+
/**
* Create a new file with the specified content type
* @param contentType
From dee28397cbe96ad3948a494153b2b7e089fb1088 Mon Sep 17 00:00:00 2001
From: Elio Struyf
Date: Fri, 22 Apr 2022 11:18:42 +0200
Subject: [PATCH 2/5] Override default content type
---
src/helpers/ContentType.ts | 70 +++++++++++++++++++++++------------
src/helpers/SettingsHelper.ts | 2 +-
2 files changed, 48 insertions(+), 24 deletions(-)
diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts
index e089a240..0834bf4a 100644
--- a/src/helpers/ContentType.ts
+++ b/src/helpers/ContentType.ts
@@ -5,7 +5,7 @@ import { ContentType as IContentType, DraftField, Field } from '../models';
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';
@@ -103,42 +103,64 @@ export class ContentType {
return;
}
- const answer = await window.showInputBox({
+ const override = await window.showQuickPick(["Yes", "No"], {
+ placeHolder: "Do you want to override the default content type?",
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;
- }
+ title: "Override default content type"
});
- if (!answer) {
- Notifications.warning(`You didn't specify a name for the content type.`);
- return;
+ let contentTypeName: string | undefined = `default`;
+
+ if (override === "No") {
+ 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;
+ }
}
const fields = ContentType.generateFields(content.data);
const newContentType: IContentType = {
- name: answer,
+ name: contentTypeName,
fields
};
const contentTypes = ContentType.getAll() || [];
- contentTypes.push(newContentType);
+
+ if (override === "Yes") {
+ const index = contentTypes.findIndex(ct => ct.name === contentTypeName);
+ contentTypes[index].fields = fields;
+ } else {
+ contentTypes.push(newContentType);
+ }
Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
- Notifications.info(`Content type ${answer} has been generated.`);
+
+ const configPath = Settings.projectConfigPath;
+ const notificationAction = await Notifications.info(`Content type ${contentTypeName} has been ${override === "Yes" ? `updated` : `generated`}.`, configPath && existsSync(configPath) ? `Open settings` : undefined);
+
+ if (notificationAction === "Open settings" && configPath && existsSync(configPath)) {
+ commands.executeCommand('vscode.open', Uri.file(configPath));
+ }
}
/**
@@ -201,6 +223,8 @@ export class ContentType {
name: field,
type: "draft"
} as Field);
+ } else if (field.toLowerCase() === "slug") {
+ // Do nothing
} else {
fields.push({
title: field,
diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts
index 7382aed3..0c93a2d7 100644
--- a/src/helpers/SettingsHelper.ts
+++ b/src/helpers/SettingsHelper.ts
@@ -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);
From 17a98fba68881e1e223dc3d9a203fc74e61ac093 Mon Sep 17 00:00:00 2001
From: Elio Struyf
Date: Mon, 25 Apr 2022 14:57:59 +0200
Subject: [PATCH 3/5] Add content type create, update, setting
---
CHANGELOG.md | 1 +
package.json | 22 +++-
src/constants/Extension.ts | 4 +-
src/constants/TelemetryEvent.ts | 5 +
src/extension.ts | 16 ++-
src/helpers/ContentType.ts | 107 +++++++++++++++++-
src/hooks/useContentType.tsx | 2 +-
src/listeners/panel/DataListener.ts | 6 +
src/panelWebView/CommandToCode.ts | 3 +
.../ContentType/ContentTypeValidator.tsx | 74 ++++++++++++
src/panelWebView/components/Metadata.tsx | 8 +-
src/panelWebView/styles.css | 27 +++++
12 files changed, 263 insertions(+), 12 deletions(-)
create mode 100644 src/panelWebView/components/ContentType/ContentTypeValidator.tsx
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f4887f1..c14c4e06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.)
diff --git a/package.json b/package.json
index 1e76debb..0b13de08 100644
--- a/package.json
+++ b/package.json
@@ -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"
}
],
diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts
index a55eb954..8cfe890d 100644
--- a/src/constants/Extension.ts
+++ b/src/constants/Extension.ts
@@ -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"),
};
\ No newline at end of file
diff --git a/src/constants/TelemetryEvent.ts b/src/constants/TelemetryEvent.ts
index b04b0232..0c6c43cd 100644
--- a/src/constants/TelemetryEvent.ts
+++ b/src/constants/TelemetryEvent.ts
@@ -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',
diff --git a/src/extension.ts b/src/extension.ts
index b761dfba..92356929 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -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);
}
}));
diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts
index 0834bf4a..829ac785 100644
--- a/src/helpers/ContentType.ts
+++ b/src/helpers/ContentType.ts
@@ -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({
diff --git a/src/hooks/useContentType.tsx b/src/hooks/useContentType.tsx
index 02111a63..f474bee4 100644
--- a/src/hooks/useContentType.tsx
+++ b/src/hooks/useContentType.tsx
@@ -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;
}
\ No newline at end of file
diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts
index faf5ba59..f696ada5 100644
--- a/src/listeners/panel/DataListener.ts
+++ b/src/listeners/panel/DataListener.ts
@@ -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);
}
}
diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts
index c0946944..30ab1986 100644
--- a/src/panelWebView/CommandToCode.ts
+++ b/src/panelWebView/CommandToCode.ts
@@ -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",
}
\ No newline at end of file
diff --git a/src/panelWebView/components/ContentType/ContentTypeValidator.tsx b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx
new file mode 100644
index 00000000..b50c758a
--- /dev/null
+++ b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx
@@ -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 = ({ fields, metadata}: React.PropsWithChildren) => {
+
+ 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 (
+
+
+
+
+
+
We noticed field differences between the content type and the front matter data.
+
+
Would you like to generate or update the content type for this page?
+
+
+ Generate content type
+
+ Add missing fields
+
+ Set content type
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx
index d866ef7e..1ae6ce24 100644
--- a/src/panelWebView/components/Metadata.tsx
+++ b/src/panelWebView/components/Metadata.tsx
@@ -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 = ({settings, metadata,
return (
+
+
{
renderFields(contentType?.fields || [], metadata)
}
diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css
index 4144d90d..29383c63 100644
--- a/src/panelWebView/styles.css
+++ b/src/panelWebView/styles.css
@@ -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;
From f10d93c22ef671a00c7e16143a63954817e63dab Mon Sep 17 00:00:00 2001
From: Elio Struyf
Date: Mon, 25 Apr 2022 20:47:44 +0200
Subject: [PATCH 4/5] Add new mode for the content type actions
---
package.json | 1 +
src/constants/Features.ts | 1 +
src/helpers/ContentType.ts | 29 +++++++++++++++++++++++-
src/listeners/general/ModeListener.ts | 24 ++++++++++++++++++++
src/panelWebView/ViewPanel.tsx | 3 ++-
src/panelWebView/components/Metadata.tsx | 13 +++++++----
6 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index 0b13de08..683dabf2 100644
--- a/package.json
+++ b/package.json
@@ -598,6 +598,7 @@
"panel.globalSettings",
"panel.seo",
"panel.actions",
+ "panel.contentType",
"panel.metadata",
"panel.recentlyModified",
"panel.otherActions",
diff --git a/src/constants/Features.ts b/src/constants/Features.ts
index eecc4df9..e4aa8629 100644
--- a/src/constants/Features.ts
+++ b/src/constants/Features.ts
@@ -8,6 +8,7 @@ export const FEATURE_FLAG = {
metadata: "panel.metadata",
recentlyModified: "panel.recentlyModified",
otherActions: "panel.otherActions",
+ contentType: "panel.contentType",
},
dashboard: {
snippets: {
diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts
index 829ac785..7eb0526e 100644
--- a/src/helpers/ContentType.ts
+++ b/src/helpers/ContentType.ts
@@ -1,6 +1,7 @@
+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, window } from 'vscode';
import { Folders } from "../commands/Folders";
@@ -97,6 +98,10 @@ export class ContentType {
* Generate a content type
*/
public static async generate() {
+ if (!(await ContentType.verify())) {
+ return;
+ }
+
Telemetry.send(TelemetryEvent.generateContentType);
const content = ArticleHelper.getCurrent();
@@ -202,6 +207,10 @@ export class ContentType {
* Add missing fields to the content type
*/
public static async addMissingFields() {
+ if (!(await ContentType.verify())) {
+ return;
+ }
+
Telemetry.send(TelemetryEvent.addMissingFields);
const content = ArticleHelper.getCurrent();
@@ -232,6 +241,10 @@ export class ContentType {
* 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();
@@ -412,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;
+ }
}
\ No newline at end of file
diff --git a/src/listeners/general/ModeListener.ts b/src/listeners/general/ModeListener.ts
index 636499d2..25989474 100644
--- a/src/listeners/general/ModeListener.ts
+++ b/src/listeners/general/ModeListener.ts
@@ -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(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);
diff --git a/src/panelWebView/ViewPanel.tsx b/src/panelWebView/ViewPanel.tsx
index 95f148d6..0cd56ead 100644
--- a/src/panelWebView/ViewPanel.tsx
+++ b/src/panelWebView/ViewPanel.tsx
@@ -70,7 +70,8 @@ export const ViewPanel: React.FunctionComponent = (props: React
settings={settings}
metadata={metadata}
focusElm={focusElm}
- unsetFocus={unsetFocus} />
+ unsetFocus={unsetFocus}
+ features={mode?.features || []} />
diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx
index 1ae6ce24..267e92c3 100644
--- a/src/panelWebView/components/Metadata.tsx
+++ b/src/panelWebView/components/Metadata.tsx
@@ -8,6 +8,8 @@ import "react-datepicker/dist/react-datepicker.css";
import useContentType from '../../hooks/useContentType';
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;
@@ -16,10 +18,11 @@ export interface IMetadataProps {
settings: PanelSettings | undefined;
metadata: IMetadata;
focusElm: TagType | null;
+ features: string[];
unsetFocus: () => void;
}
-const Metadata: React.FunctionComponent = ({settings, metadata, focusElm, unsetFocus}: React.PropsWithChildren) => {
+const Metadata: React.FunctionComponent = ({settings, features, metadata, focusElm, unsetFocus}: React.PropsWithChildren) => {
const contentType = useContentType(settings, metadata);
const sendUpdate = (field: string | undefined, value: any, parents: string[]) => {
@@ -78,9 +81,11 @@ const Metadata: React.FunctionComponent = ({settings, metadata,
return (
-
+
+
+
{
renderFields(contentType?.fields || [], metadata)
From d161aa98a03895e733f9ceee27142b01c2c90d9e Mon Sep 17 00:00:00 2001
From: Elio Struyf
Date: Tue, 26 Apr 2022 12:05:08 +0200
Subject: [PATCH 5/5] Updates for button colors
---
assets/media/styles.css | 5 +++++
.../components/ContentType/ContentTypeValidator.tsx | 4 ++--
src/panelWebView/styles.css | 2 +-
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/assets/media/styles.css b/assets/media/styles.css
index b349f165..acd7388f 100644
--- a/assets/media/styles.css
+++ b/assets/media/styles.css
@@ -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;
}
diff --git a/src/panelWebView/components/ContentType/ContentTypeValidator.tsx b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx
index b50c758a..f084a9bf 100644
--- a/src/panelWebView/components/ContentType/ContentTypeValidator.tsx
+++ b/src/panelWebView/components/ContentType/ContentTypeValidator.tsx
@@ -58,10 +58,10 @@ export const ContentTypeValidator: React.FunctionComponentWe noticed field differences between the content type and the front matter data.
- Would you like to generate or update the content type for this page?
+ Would you like to create, update, or set the content type for this content?
- Generate content type
+ Create content type
Add missing fields
diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css
index 29383c63..ba088adc 100644
--- a/src/panelWebView/styles.css
+++ b/src/panelWebView/styles.css
@@ -277,7 +277,7 @@ vscode-divider {
}
.inline_hint {
- color: var(--vscode-editorInlayHint-foreground);
+ color: var(--vscode-sideBar-foreground);
}
/* File field */