#376: post script functionality

This commit is contained in:
Elio Struyf
2022-08-04 15:23:41 +02:00
parent 670791fcf6
commit c8ecc92309
11 changed files with 104 additions and 48 deletions
+1
View File
@@ -4,6 +4,7 @@
### ✨ New features
- [#376](https://github.com/estruyf/vscode-front-matter/issues/376): Ability to run scripts after content was created
- [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote)
### 🎨 Enhancements
+14
View File
@@ -363,6 +363,10 @@
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "ID of the script."
},
"title": {
"type": "string",
"description": "Title you want to give to your script. Will be shown as the title of the button."
@@ -419,6 +423,11 @@
],
"description": "The type of script you want to execute.",
"default": "node"
},
"hidden": {
"type": "boolean",
"description": "Hide the action from the UI",
"default": false
}
},
"additionalProperties": false,
@@ -1123,6 +1132,11 @@
"type": "string",
"default": "",
"description": "An optional template that can be used for creating new content."
},
"postScript": {
"type": "string",
"default": "",
"description": "An optional post script that can be used after new content creation."
}
},
"additionalProperties": false,
@@ -12,11 +12,10 @@ export interface IChoiceButtonProps {
onClick: () => void;
}[];
disabled?: boolean;
isTemplatesEnabled?: boolean;
onClick: () => void;
}
export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({onClick, disabled, choices, isTemplatesEnabled, title}: React.PropsWithChildren<IChoiceButtonProps>) => {
export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({onClick, disabled, choices, title}: React.PropsWithChildren<IChoiceButtonProps>) => {
return (
<span className="relative z-50 inline-flex shadow-sm rounded-md">
<button
@@ -29,7 +28,7 @@ export const ChoiceButton: React.FunctionComponent<IChoiceButtonProps> = ({onCli
</button>
{
isTemplatesEnabled && (
choices.length > 0 && (
<Menu as="span" className="-ml-px relative block">
<Menu.Button
className="h-full inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-700 hover:bg-teal-800 focus:outline-none disabled:bg-gray-500"
@@ -40,7 +40,7 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
}, [path]);
const customScriptActions = React.useMemo(() => {
return (scripts || []).filter(script => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk).map(script => (
return (scripts || []).filter(script => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk && !script.hidden).map(script => (
<MenuItem
key={script.title}
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
@@ -21,7 +21,7 @@ import { CustomScript } from '../../../models';
import { LightningBoltIcon, PlusIcon } from '@heroicons/react/outline';
import { useLocation, useNavigate } from 'react-router-dom';
import { routePaths } from '../..';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { SyncButton } from './SyncButton';
export interface IHeaderProps {
@@ -73,6 +73,38 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPage
onClick: () => runBulkScript(s)
}));
const choiceOptions = useMemo(() => {
const isEnabled = settings?.dashboardState?.contents?.templatesEnabled || false;
if (isEnabled) {
return [
{
title: (
<div className='flex items-center'>
<PlusIcon className="w-4 h-4 mr-2" />
<span>Create by content type</span>
</div>
),
onClick: createByContentType,
disabled: !settings?.initialized
}, {
title: (
<div className='flex items-center'>
<PlusIcon className="w-4 h-4 mr-2" />
<span>Create by template</span>
</div>
),
onClick: createByTemplate,
disabled: !settings?.initialized
},
...customActions
];
}
return [];
}, [settings?.dashboardState?.contents?.templatesEnabled]);
useEffect(() => {
if (location.search) {
const searchParams = new URLSearchParams(location.search);
@@ -114,30 +146,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPage
<ChoiceButton
title={`Create content`}
choices={[
{
title: (
<div className='flex items-center'>
<PlusIcon className="w-4 h-4 mr-2" />
<span>Create by content type</span>
</div>
),
onClick: createByContentType,
disabled: !settings?.initialized
}, {
title: (
<div className='flex items-center'>
<PlusIcon className="w-4 h-4 mr-2" />
<span>Create by template</span>
</div>
),
onClick: createByTemplate,
disabled: !settings?.initialized
},
...customActions
]}
onClick={createContent}
isTemplatesEnabled={settings?.dashboardState?.contents?.templatesEnabled || undefined}
choices={choiceOptions}
onClick={createContent}
disabled={!settings?.initialized} />
</div>
</div>
@@ -23,7 +23,8 @@ export const FolderCreation: React.FunctionComponent<IFolderCreationProps> = (pr
Messenger.send(DashboardMessage.runCustomScript, {script, path: selectedFolder});
};
const scripts = (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFolder);
const scripts = (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFolder && !script.hidden);
if (scripts.length > 0) {
return (
<div className="flex flex-1 justify-end">
@@ -225,7 +225,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
};
const customScriptActions = () => {
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile).map(script => (
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile && !script.hidden).map(script => (
<MenuItem
key={script.title}
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
@@ -259,7 +259,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const extension = path.split('.').pop();
let icon = <DocumentIcon className={`h-4/6 text-gray-300 dark:text-vulcan-200`} />;
console.log(media);
if (isImageFile) {
return <PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />;
+16 -3
View File
@@ -1,9 +1,9 @@
import { ModeListener } from './../listeners/general/ModeListener';
import { PagesListener } from './../listeners/dashboard';
import { ArticleHelper, Settings } from ".";
import { ArticleHelper, CustomScript, Settings } from ".";
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_FRAMEWORK_ID, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_FIELD_GROUPS, TelemetryEvent } from "../constants";
import { ContentType as IContentType, DraftField, Field, FieldGroup, FieldType } from '../models';
import { Uri, commands, window, ProgressLocation } from 'vscode';
import { ContentType as IContentType, DraftField, Field, FieldGroup, FieldType, ScriptType } from '../models';
import { Uri, commands, window, ProgressLocation, workspace } from 'vscode';
import { Folders } from "../commands/Folders";
import { Questions } from "./Questions";
import { existsSync, writeFileSync } from "fs";
@@ -543,6 +543,19 @@ export class ContentType {
writeFileSync(newFilePath, content, { encoding: "utf8" });
// Check if the content type has a post script to execute
if (contentType.postScript) {
const scripts = await CustomScript.getScripts();
const script = scripts.find(s => s.id === contentType.postScript);
if (script && (script.type === ScriptType.Content || !script?.type)) {
await CustomScript.run(script, newFilePath);
const doc = await workspace.openTextDocument(Uri.file(newFilePath));
await doc.save();
}
}
await commands.executeCommand('vscode.open', Uri.file(newFilePath));
Notifications.info(`Your new content has been created.`);
+27 -11
View File
@@ -1,3 +1,4 @@
import { Settings } from './SettingsHelper';
import { CommandType } from './../models/PanelSettings';
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
@@ -12,9 +13,24 @@ import { Dashboard } from '../commands/Dashboard';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { ParsedFrontMatter } from '../parsers';
import { TelemetryEvent } from '../constants/TelemetryEvent';
import { SETTING_CUSTOM_SCRIPTS } from '../constants';
export class CustomScript {
/**
* Retrieve all scripts
* @returns
*/
public static async getScripts(): Promise<ICustomScript[]> {
const scripts = Settings.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || [];
return scripts;
}
/**
* Run a script
* @param script
* @param path
*/
public static async run(script: ICustomScript, path: string | null = null): Promise<void> {
const wsFolder = Folders.getWorkspaceFolder();
@@ -24,19 +40,19 @@ export class CustomScript {
if (script.type === ScriptType.MediaFile || script.type === ScriptType.MediaFolder) {
Telemetry.send(TelemetryEvent.runMediaScript);
CustomScript.runMediaScript(wsPath, path, script);
await CustomScript.runMediaScript(wsPath, path, script);
} else {
Telemetry.send(TelemetryEvent.runCustomScript);
if (script.bulk) {
// Run script on all files
CustomScript.bulkRun(wsPath, script);
await CustomScript.bulkRun(wsPath, script);
} else if (path) {
// Run script for provided path
CustomScript.singleRun(wsPath, script, path);
await CustomScript.singleRun(wsPath, script, path);
} else {
// Run script on current file.
CustomScript.singleRun(wsPath, script);
await CustomScript.singleRun(wsPath, script);
}
}
}
@@ -64,13 +80,13 @@ export class CustomScript {
}
if (articlePath && article) {
window.withProgress({
return window.withProgress({
location: ProgressLocation.Notification,
title: `Executing: ${script.title}`,
cancellable: false
}, async () => {
const output = await CustomScript.runScript(wsPath, article, articlePath as string, script);
CustomScript.showOutput(output, script, articlePath);
await CustomScript.showOutput(output, script, articlePath);
});
} else {
Notifications.warning(`${script.title}: Article couldn't be retrieved.`);
@@ -116,7 +132,7 @@ export class CustomScript {
}
}
CustomScript.showOutput(output.join(`\n`), script);
await CustomScript.showOutput(output.join(`\n`), script);
});
}
@@ -142,7 +158,7 @@ export class CustomScript {
try {
const output = await CustomScript.executeScript(script, wsPath, `"${wsPath}" "${path}"`);
CustomScript.showOutput(output, script);
await CustomScript.showOutput(output, script);
Dashboard.postWebviewMessage({
command: DashboardCommand.mediaUpdate
@@ -188,7 +204,7 @@ export class CustomScript {
* @param output
* @param script
*/
private static showOutput(output: string | null, script: ICustomScript, articlePath?: string | null): void {
private static async showOutput(output: string | null, script: ICustomScript, articlePath?: string | null): Promise<void> {
if (output) {
try {
const data = JSON.parse(output);
@@ -212,9 +228,9 @@ export class CustomScript {
}
if (articlePath) {
ArticleHelper.updateByPath(articlePath, article);
await ArticleHelper.updateByPath(articlePath, article);
} else if (editor) {
ArticleHelper.update(editor, article);
await ArticleHelper.update(editor, article);
} else {
throw new Error(`Couldn't update article.`);
}
+1 -1
View File
@@ -36,7 +36,7 @@ export class PanelSettings {
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => (s.type === ScriptType.Content || !s.type) && !s.hidden),
isInitialized: await Template.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
+4 -1
View File
@@ -48,6 +48,7 @@ export interface ContentType {
previewPath?: string | null;
pageBundle?: boolean;
template?: string;
postScript?: string;
}
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile" | "list" | "slug";
@@ -112,6 +113,7 @@ export interface FileInfo extends FileStat {
};
export interface CustomScript {
id?: string;
title: string;
script: string;
nodeBin?: string;
@@ -120,6 +122,7 @@ export interface CustomScript {
outputType?: string;
type?: ScriptType;
command?: CommandType | string;
hidden?: boolean;
}
export interface PreviewSettings {
@@ -135,7 +138,7 @@ export interface CustomTaxonomy {
export enum ScriptType {
Content = "content",
MediaFolder = "mediaFolder",
MediaFile = "mediaFile"
MediaFile = "mediaFile",
}
export enum CommandType {