Skip to content

Commit e38f101

Browse files
committed
FEATURE: Introduce support for Shel.Neos.CommandBar
With this change all terminal commands also show up in the commandbar.
1 parent 4cc4ae6 commit e38f101

File tree

7 files changed

+183
-31
lines changed

7 files changed

+183
-31
lines changed

Readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ It uses the great [terminal component](https://github.com/linuswillner/react-con
2121
* Open Terminal via `t+t` shortcut
2222
* Limit commands to backend roles
2323
* Create your own commands and provide them in your own packages
24+
* Full support for the [Shel.Neos.CommandBar](https://github.com/Sebobo/Shel.Neos.CommandBar)
2425

2526
## How it looks
2627

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import manifest from '@neos-project/neos-ui-extensibility';
2+
23
import { reducer, actions } from './actions';
34
import Terminal from './Terminal';
5+
import getTerminalCommandRegistry from './registry/TerminalCommandRegistry';
46

57
window['NeosTerminal'] = window.NeosTerminal || {};
68

7-
manifest('Shel.Neos.Terminal:Terminal', {}, (globalRegistry, { frontendConfiguration }) => {
8-
const { enabled } = frontendConfiguration['Shel.Neos.Terminal:Terminal'];
9+
manifest('Shel.Neos.Terminal:Terminal', {}, (globalRegistry, { store, frontendConfiguration }) => {
10+
const config = frontendConfiguration['Shel.Neos.Terminal:Terminal'];
911

10-
if (!enabled) return;
12+
if (!config.enabled) return;
1113

1214
globalRegistry.get('reducers').set('Shel.Neos.Terminal', { reducer });
1315
globalRegistry.get('containers').set('PrimaryToolbar/Middle/Terminal', Terminal);
@@ -18,4 +20,14 @@ manifest('Shel.Neos.Terminal:Terminal', {}, (globalRegistry, { frontendConfigura
1820
action: actions.toggleNeosTerminal,
1921
});
2022
}
23+
24+
// Register test plugin command
25+
const commandBarRegistry = globalRegistry.get('Shel.Neos.CommandBar');
26+
if (commandBarRegistry) {
27+
commandBarRegistry.set('plugins/terminal', async () => {
28+
const i18nRegistry = globalRegistry.get('i18n');
29+
const terminalCommandRegistry = getTerminalCommandRegistry(config, i18nRegistry, store);
30+
return terminalCommandRegistry.getCommandsForCommandBar();
31+
});
32+
}
2133
});

Resources/Private/JavaScript/Terminal/src/provider/CommandsProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const logToConsole = (type = 'log', text: string, ...args) => {
4646
console[type](`%c[Neos.Terminal]%c ${text}:`, finalStyle, ConsoleStyle.text.join(';'), ...args);
4747
};
4848

49+
// TODO: Either provider or use TerminalCommandRegistry instead
4950
export const CommandsProvider = ({
5051
invokeCommandEndPoint,
5152
getCommandsEndPoint,
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from 'react';
2+
3+
// @ts-ignore
4+
import { selectors } from '@neos-project/neos-ui-redux-store';
5+
6+
import fetchCommands from '../helpers/fetchCommands';
7+
import { CommandList, I18nRegistry, NeosRootState } from '../interfaces';
8+
import doInvokeCommand from '../helpers/doInvokeCommand';
9+
import Command from '../interfaces/Command';
10+
11+
interface NeosStore {
12+
getState: () => NeosRootState;
13+
dispatch: () => void;
14+
}
15+
16+
// noinspection JSPotentiallyInvalidUsageOfClassThis
17+
class TerminalCommandRegistry {
18+
constructor(readonly config: TerminalConfig, readonly i18nRegistry: I18nRegistry, readonly store: NeosStore) {
19+
this.invokeCommand = this.invokeCommand.bind(this);
20+
}
21+
22+
private commands: CommandList;
23+
24+
public getCommands = async () => {
25+
if (this.commands) return this.commands;
26+
return (this.commands = await fetchCommands(this.config.getCommandsEndPoint).then(({ result }) => result));
27+
};
28+
29+
public translate = (
30+
id: string,
31+
fallback = '',
32+
params: Record<string, unknown> | string[] = [],
33+
packageKey = 'Shel.Neos.Terminal',
34+
sourceName = 'Main'
35+
): string => {
36+
return this.i18nRegistry.translate(id, fallback, params, packageKey, sourceName);
37+
};
38+
39+
public getCommandsForCommandBar = async () => {
40+
const commands = await this.getCommands();
41+
const invokeCommand = this.invokeCommand;
42+
return {
43+
'shel.neos.terminal': {
44+
name: 'Terminal',
45+
description: 'Execute terminal commands',
46+
icon: 'terminal',
47+
subCommands: Object.values(commands).reduce((acc, { name, description }) => {
48+
acc[name] = {
49+
name,
50+
icon: 'terminal',
51+
description: this.translate(description),
52+
action: async function* (arg) {
53+
yield* invokeCommand(name, arg);
54+
},
55+
canHandleQueries: true,
56+
executeManually: true,
57+
};
58+
return acc;
59+
}, {}),
60+
},
61+
};
62+
};
63+
64+
public invokeCommand = async function* (commandName: string, arg = '') {
65+
const state = this.store.getState();
66+
const siteNode = selectors.CR.Nodes.siteNodeSelector(state);
67+
const documentNode = selectors.CR.Nodes.documentNodeSelector(state);
68+
const focusedNodes = selectors.CR.Nodes.focusedNodePathsSelector(state);
69+
const command = this.commands[commandName] as Command;
70+
71+
if (!arg) {
72+
yield {
73+
success: true,
74+
message: this.translate(
75+
'TerminalCommandRegistry.message.provideArguments',
76+
`Please provide arguments for command "${commandName}"`,
77+
{ commandName }
78+
),
79+
view: (
80+
<div>
81+
<p>{this.translate(command.description)}</p>
82+
<code>{command.usage}</code>
83+
</div>
84+
),
85+
};
86+
} else {
87+
const response = await doInvokeCommand(
88+
this.config.invokeCommandEndPoint,
89+
commandName,
90+
[arg],
91+
siteNode.contextPath,
92+
focusedNodes[0]?.contextPath,
93+
documentNode.contextPath
94+
);
95+
96+
let result = response.result;
97+
98+
// Try to prettify json results
99+
try {
100+
const parsedResult = JSON.parse(result);
101+
if (typeof parsedResult !== 'string') {
102+
result = (
103+
<pre>
104+
<code>{JSON.stringify(parsedResult, null, 2)}</code>
105+
</pre>
106+
);
107+
} else {
108+
result = <p>{result.replace(/\\n/g, '\n')}</p>;
109+
}
110+
} catch (e) {
111+
// Treat result as simple string
112+
}
113+
114+
yield {
115+
success: response.success,
116+
message: this.translate(
117+
'TerminalCommandRegistry.message.result',
118+
`Result of command "${commandName}"`,
119+
{ commandName }
120+
),
121+
view: result,
122+
};
123+
}
124+
};
125+
}
126+
127+
let singleton = null;
128+
129+
export default function getTerminalCommandRegistry(
130+
config: TerminalConfig,
131+
i18nRegistry: I18nRegistry,
132+
store: NeosStore
133+
): TerminalCommandRegistry {
134+
return singleton ?? (singleton = new TerminalCommandRegistry(config, i18nRegistry, store));
135+
}

Resources/Public/Assets/Plugin.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Resources/Public/Assets/Plugin.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composer.json

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
{
2-
"description": "Neos CMS Ui terminal for running Eel expressions and other commands",
3-
"type": "neos-plugin",
4-
"name": "shel/neos-terminal",
5-
"license": "MIT",
6-
"keywords": [
7-
"flow",
8-
"neoscms",
9-
"terminal",
10-
"console",
11-
"eel"
12-
],
13-
"require": {
14-
"php": ">=7.4",
15-
"neos/neos": "^7.3 || ^8.0",
16-
"neos/neos-ui": "^7.3 || ^8.0",
17-
"symfony/console": "^4.2 || ^5.1"
18-
},
19-
"autoload": {
20-
"psr-4": {
21-
"Shel\\Neos\\Terminal\\": "Classes"
22-
}
23-
},
24-
"extra": {
25-
"neos": {
26-
"package-key": "Shel.Neos.Terminal"
27-
}
2+
"description": "Neos CMS Ui terminal for running Eel expressions and other commands",
3+
"type": "neos-plugin",
4+
"name": "shel/neos-terminal",
5+
"license": "MIT",
6+
"keywords": [
7+
"flow",
8+
"neoscms",
9+
"terminal",
10+
"console",
11+
"eel"
12+
],
13+
"require": {
14+
"php": ">=7.4",
15+
"neos/neos": "^7.3 || ^8.0",
16+
"neos/neos-ui": "^7.3 || ^8.0",
17+
"symfony/console": "^4.2 || ^5.1"
18+
},
19+
"suggest": {
20+
"shel/neos-commandbar": "The terminal provides a plugin integration for the Neos command bar"
21+
},
22+
"autoload": {
23+
"psr-4": {
24+
"Shel\\Neos\\Terminal\\": "Classes"
2825
}
26+
},
27+
"extra": {
28+
"neos": {
29+
"package-key": "Shel.Neos.Terminal"
30+
}
31+
}
2932
}
3033

0 commit comments

Comments
 (0)