Rework .reg importing in script editor
Script editor now supports drag and drop for .ps1, .reg, and other text files. .ps1 files will be handled by replacing the contents of the text editor. .reg files will be converted to PowerShell commands and inserted into the script editor. Any other text file will be inserted as an escaped string.
This commit is contained in:
parent
37caa8ab9b
commit
ca713f6d3f
6 changed files with 251 additions and 8 deletions
|
|
@ -80,8 +80,6 @@
|
|||
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
|
||||
}
|
||||
|
||||
<Button Icon="@IconType.Outline.Build" OnClick="() => _regToPowerShell.Open()" Type="@ButtonType.Text">Import .reg</Button>
|
||||
|
||||
@if (IsDebuggable && HasDebugTarget)
|
||||
{
|
||||
<Tooltip Title="Debug">
|
||||
|
|
@ -107,14 +105,12 @@
|
|||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<RegToPowerShell @ref="_regToPowerShell" OnParsed="(text) => InsertText(text)" />
|
||||
|
||||
@code {
|
||||
Guid _id = Guid.NewGuid();
|
||||
|
||||
Form<Script>? _form;
|
||||
CodeInput? _codeInput;
|
||||
RegToPowerShell? _regToPowerShell;
|
||||
PowerShellConsole? _console;
|
||||
IEnumerable<Snippet> Snippets { get; set; } = [];
|
||||
List<string> _variables = new();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
|
||||
}
|
||||
|
||||
<Button Icon="@IconType.Outline.Build" OnClick="() => RegToPowerShell.Open()" Type="@ButtonType.Text">Import .reg</Button>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
|
|
@ -58,7 +57,6 @@
|
|||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<RegToPowerShell @ref="RegToPowerShell" OnParsed="(text) => InsertText(text)" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Script Script { get; set; } = new();
|
||||
|
|
@ -70,7 +68,6 @@
|
|||
Guid Id = Guid.NewGuid();
|
||||
Form<Script> Form;
|
||||
MonacoCodeEditor? Editor;
|
||||
RegToPowerShell RegToPowerShell;
|
||||
IEnumerable<Snippet> Snippets { get; set; }
|
||||
|
||||
StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@
|
|||
await _module.InvokeVoidAsync("setScriptType", ScriptType);
|
||||
}
|
||||
|
||||
if (Language.Id == "powershell")
|
||||
await _module.InvokeVoidAsync("SetupFileDropHandler");
|
||||
|
||||
if (OnReady.HasDelegate)
|
||||
await OnReady.InvokeAsync();
|
||||
}
|
||||
|
|
|
|||
244
LANCommander.UI/Components/CodeInput/FileDropHandler.ts
Normal file
244
LANCommander.UI/Components/CodeInput/FileDropHandler.ts
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
declare const monaco: any;
|
||||
|
||||
export function SetupFileDropHandler(): void {
|
||||
if (typeof monaco === "undefined")
|
||||
return;
|
||||
|
||||
const editors = monaco.editor.getEditors();
|
||||
const editor = editors[editors.length - 1];
|
||||
|
||||
if (!editor)
|
||||
return;
|
||||
|
||||
const domNode = editor.getDomNode();
|
||||
|
||||
if (!domNode)
|
||||
return;
|
||||
|
||||
domNode.addEventListener("dragover", (e: DragEvent) => {
|
||||
if (e.dataTransfer?.types?.includes("Files")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
domNode.addEventListener("drop", (e: DragEvent) => {
|
||||
const files = e.dataTransfer?.files;
|
||||
|
||||
if (files && files.length > 0) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleFile(editor, files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("paste", (e: ClipboardEvent) => {
|
||||
if (!editor.hasWidgetFocus())
|
||||
return;
|
||||
|
||||
if (!e.clipboardData?.files.length)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
handleFile(editor, e.clipboardData.files[0]);
|
||||
}, true);
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 1024 * 1024; // 1 MB
|
||||
|
||||
function IsTextFile(file: File): boolean {
|
||||
if (file.type.startsWith("text/")) return true;
|
||||
|
||||
const textExtensions = [
|
||||
".reg", ".ps1", ".txt", ".cfg", ".ini", ".conf",
|
||||
".json", ".xml", ".yaml", ".yml", ".csv", ".log",
|
||||
".bat", ".cmd", ".sh", ".py", ".lua",
|
||||
];
|
||||
|
||||
return textExtensions.includes(getFileExtension(file.name));
|
||||
}
|
||||
|
||||
function handleFile(editor: any, file: File): void {
|
||||
if (file.size > MAX_FILE_SIZE)
|
||||
return;
|
||||
|
||||
if (!IsTextFile(file))
|
||||
return;
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
const content = reader.result as string;
|
||||
const ext = getFileExtension(file.name);
|
||||
|
||||
if (ext === ".reg") {
|
||||
const psCommands = ParseRegToPowerShell(content);
|
||||
insertTextAtCursor(editor, psCommands);
|
||||
} else if (ext === ".ps1") {
|
||||
editor.setValue(content);
|
||||
} else {
|
||||
const hereString = `@'\n${content}\n'@`;
|
||||
|
||||
insertTextAtCursor(editor, hereString);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function getFileExtension(filename: string): string {
|
||||
const idx = filename.lastIndexOf(".");
|
||||
|
||||
return idx >= 0 ? filename.substring(idx).toLowerCase() : "";
|
||||
}
|
||||
|
||||
function insertTextAtCursor(editor: any, text: string): void {
|
||||
const selection = editor.getSelection();
|
||||
|
||||
editor.executeEdits("fileImport", [{
|
||||
range: selection,
|
||||
text: text,
|
||||
forceMoveMarkers: true,
|
||||
}]);
|
||||
}
|
||||
|
||||
interface RegEntry {
|
||||
type: "key" | "sz" | "dword" | "binary";
|
||||
path: string;
|
||||
property?: string;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
function ParseRegToPowerShell(regContent: string): string {
|
||||
const entries = ParseRegFile(regContent);
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
switch (entry.type) {
|
||||
case "key":
|
||||
if (lines.length > 0)
|
||||
lines.push("");
|
||||
|
||||
lines.push(`New-Item -Path "registry::\\${entry.path}"`);
|
||||
break;
|
||||
case "sz": {
|
||||
const escaped = (entry.value as string).replace(/"/g, '""""');
|
||||
|
||||
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -Value "${escaped}" -Force`);
|
||||
break;
|
||||
}
|
||||
case "dword":
|
||||
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -Value ${entry.value} -Force`);
|
||||
break;
|
||||
case "binary": {
|
||||
const bytes = entry.value as number[];
|
||||
const chunks: string[] = [];
|
||||
|
||||
for (let i = 0; i < bytes.length; i += 32) {
|
||||
const chunk = bytes.slice(i, i + 32);
|
||||
chunks.push(chunk.map(b => "0x" + b.toString(16).toUpperCase().padStart(2, "0")).join(", "));
|
||||
}
|
||||
|
||||
const formatted = chunks.join("\n\t");
|
||||
|
||||
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -PropertyType Binary -Value ([byte[]]@(\n\t${formatted}\n)) -Force`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function ParseRegFile(content: string): RegEntry[] {
|
||||
const entries: RegEntry[] = [];
|
||||
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
const joined = normalized.replace(/\\\n\s*/g, "");
|
||||
const rawLines = joined.split("\n");
|
||||
|
||||
let currentPath = "";
|
||||
|
||||
for (const line of rawLines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Skip empty lines, comments, and headers
|
||||
if (!trimmed || trimmed.startsWith(";") ||
|
||||
trimmed === "Windows Registry Editor Version 5.00" ||
|
||||
trimmed === "REGEDIT4") {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key section: [HKEY_...]
|
||||
const keyMatch = trimmed.match(/^\[(.+)\]$/);
|
||||
|
||||
if (keyMatch) {
|
||||
const path = keyMatch[1];
|
||||
|
||||
if (path.startsWith("-"))
|
||||
continue;
|
||||
currentPath = path;
|
||||
entries.push({ type: "key", path: currentPath });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!currentPath)
|
||||
continue;
|
||||
|
||||
// Parse value lines
|
||||
let name: string;
|
||||
let valueStr: string;
|
||||
|
||||
if (trimmed.startsWith("@=")) {
|
||||
name = "@";
|
||||
valueStr = trimmed.substring(2);
|
||||
} else {
|
||||
const valueMatch = trimmed.match(/^"(.+?)"=(.*)$/);
|
||||
|
||||
if (!valueMatch)
|
||||
continue;
|
||||
|
||||
name = valueMatch[1].replace(/\\\\/g, "\\").replace(/\\"/g, '"');
|
||||
valueStr = valueMatch[2];
|
||||
}
|
||||
|
||||
// Skip delete values
|
||||
if (valueStr === "-")
|
||||
continue;
|
||||
|
||||
// String value: "..."
|
||||
if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
||||
const str = valueStr.slice(1, -1).replace(/\\\\/g, "\\").replace(/\\"/g, '"');
|
||||
|
||||
entries.push({ type: "sz", path: currentPath, property: name, value: str });
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// DWORD: dword:XXXXXXXX
|
||||
const dwordMatch = valueStr.match(/^dword:([0-9a-fA-F]{1,8})$/);
|
||||
|
||||
if (dwordMatch) {
|
||||
const val = parseInt(dwordMatch[1], 16);
|
||||
|
||||
entries.push({ type: "dword", path: currentPath, property: name, value: val });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Binary/hex types: hex:XX,XX,... or hex(N):XX,XX,...
|
||||
const hexMatch = valueStr.match(/^hex(?:\([0-9a-fA-F]+\))?:(.*)$/);
|
||||
|
||||
if (hexMatch) {
|
||||
const hexStr = hexMatch[1].replace(/\s/g, "");
|
||||
const bytes = hexStr.split(",")
|
||||
.filter(s => s.length > 0)
|
||||
.map(s => parseInt(s, 16));
|
||||
|
||||
entries.push({ type: "binary", path: currentPath, property: name, value: bytes });
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
|
@ -67,6 +67,8 @@ public class MonacoCodeEditor : StandaloneCodeEditor
|
|||
_previousScriptType = ScriptType;
|
||||
await _module.InvokeVoidAsync("setScriptType", ScriptType);
|
||||
}
|
||||
|
||||
await _module.InvokeVoidAsync("SetupFileDropHandler");
|
||||
}
|
||||
|
||||
private async Task OnChanged(ModelContentChangedEvent e)
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ export { Terminal } from "./Components/Terminal/Terminal";
|
|||
export { DomHelper } from "./Components/DomHelper/DomHelper";
|
||||
export { registerPowerShellCompletions, setScriptType, validateScript, insertSnippet, getScriptTemplate } from "./Components/MonacoCodeEditor/PowerShellCompletionProvider";
|
||||
export { registerYamlCompletions } from "./Components/MonacoCodeEditor/YamlCompletionProvider";
|
||||
export { UploadManager } from "./Components/UploadManager/UploadManager";
|
||||
export { UploadManager } from "./Components/UploadManager/UploadManager";
|
||||
export { SetupFileDropHandler } from "./Components/CodeInput/FileDropHandler";
|
||||
Loading…
Add table
Add a link
Reference in a new issue