mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-06 17:02:54 +02:00
#376: post script functionality
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`} />;
|
||||
|
||||
@@ -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
@@ -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.`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user