feat: delete fields tool
fix: field rename fix: list fields now scrolls
This commit is contained in:
parent
7a3b3f2161
commit
7c19ada88b
@ -1,7 +1,7 @@
|
|||||||
import { cliPrompt } from "./prompts.ts";
|
import { cliPrompt } from "./prompts.ts";
|
||||||
import type { TerminalBlock } from "./TerminalLayout.ts";
|
import type { TerminalBlock } from "./TerminalLayout.ts";
|
||||||
|
|
||||||
type prompt = [string, (v?: string) => boolean] | string;
|
type prompt = [string, (v?: string) => boolean | undefined] | string;
|
||||||
|
|
||||||
export async function forceArgs(
|
export async function forceArgs(
|
||||||
args: string[],
|
args: string[],
|
||||||
|
@ -13,6 +13,7 @@ const toolRegistry: [string, Promise<{ default: ITool }>][] = [
|
|||||||
["checkCode", import("../tools/checkCode.ts")],
|
["checkCode", import("../tools/checkCode.ts")],
|
||||||
["fieldRename", import("../tools/fieldRename.ts")],
|
["fieldRename", import("../tools/fieldRename.ts")],
|
||||||
["listFormFields", import("../tools/listFormFields.ts")],
|
["listFormFields", import("../tools/listFormFields.ts")],
|
||||||
|
["deleteFields", import("../tools/deleteFields.ts")],
|
||||||
];
|
];
|
||||||
|
|
||||||
export class PdfToolsCli {
|
export class PdfToolsCli {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { callback } from "../types.ts";
|
import type { callback } from "../types.ts";
|
||||||
|
import { log } from "util/logfile.ts";
|
||||||
import { type CLICharEvent, InputManager } from "./InputManager.ts";
|
import { type CLICharEvent, InputManager } from "./InputManager.ts";
|
||||||
import { cliLog } from "./prompts.ts";
|
import { cliLog } from "./prompts.ts";
|
||||||
import { colorize } from "./style.ts";
|
import { colorize } from "./style.ts";
|
||||||
@ -127,13 +128,16 @@ export async function selectMenuInteractive(
|
|||||||
|
|
||||||
export async function multiSelectMenuInteractive(
|
export async function multiSelectMenuInteractive(
|
||||||
q: string,
|
q: string,
|
||||||
options: string[] | [string, callback][],
|
options: (string | [string, callback])[],
|
||||||
config?: ISelectMenuConfig,
|
config?: ISelectMenuConfig & { allOption?: boolean },
|
||||||
): Promise<string[] | null> {
|
): Promise<string[] | null> {
|
||||||
Deno.stdin.setRaw(true);
|
Deno.stdin.setRaw(true);
|
||||||
let selected = 0;
|
let selected = 0;
|
||||||
let selectedOptions: number[] = config?.initialSelections || [];
|
let selectedOptions: number[] = config?.initialSelections || [];
|
||||||
|
|
||||||
|
if (config?.allOption) {
|
||||||
|
options.unshift("Select All");
|
||||||
|
}
|
||||||
const rawValues = options.map((i) => typeof i === "string" ? i : i[0]);
|
const rawValues = options.map((i) => typeof i === "string" ? i : i[0]);
|
||||||
|
|
||||||
if (rawValues.length !== options.length) {
|
if (rawValues.length !== options.length) {
|
||||||
@ -145,6 +149,21 @@ export async function multiSelectMenuInteractive(
|
|||||||
terminalBlock.setRenderHeight(Deno.consoleSize().rows);
|
terminalBlock.setRenderHeight(Deno.consoleSize().rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkSelectAll = () => {
|
||||||
|
if (selectedOptions.includes(0)) {
|
||||||
|
log("yeet");
|
||||||
|
selectedOptions = [];
|
||||||
|
} else {
|
||||||
|
log("neat");
|
||||||
|
selectedOptions = Array.from(options).map((_, i) => i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateSelectAll = () => {
|
||||||
|
const allPresent = selectedOptions.length == options.length;
|
||||||
|
if (!allPresent) selectedOptions = selectedOptions.filter((e) => e != 0);
|
||||||
|
};
|
||||||
|
|
||||||
let range: [number, number] = [terminalBlock.lineCount, 1];
|
let range: [number, number] = [terminalBlock.lineCount, 1];
|
||||||
function renderMenu() {
|
function renderMenu() {
|
||||||
const { rows } = Deno.consoleSize();
|
const { rows } = Deno.consoleSize();
|
||||||
@ -193,11 +212,14 @@ export async function multiSelectMenuInteractive(
|
|||||||
const onSpace = (e: CLICharEvent) => {
|
const onSpace = (e: CLICharEvent) => {
|
||||||
if (e.detail.char !== " ") return;
|
if (e.detail.char !== " ") return;
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
if (selectedOptions.includes(selected)) {
|
if (config?.allOption && selected === 0) {
|
||||||
|
checkSelectAll();
|
||||||
|
} else if (selectedOptions.includes(selected)) {
|
||||||
selectedOptions = selectedOptions.filter((i) => i !== selected);
|
selectedOptions = selectedOptions.filter((i) => i !== selected);
|
||||||
} else {
|
} else {
|
||||||
selectedOptions.push(selected);
|
selectedOptions.push(selected);
|
||||||
}
|
}
|
||||||
|
validateSelectAll();
|
||||||
renderMenu();
|
renderMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -299,7 +321,7 @@ if (import.meta.main) {
|
|||||||
"ximenia",
|
"ximenia",
|
||||||
"yuzu",
|
"yuzu",
|
||||||
"zucchini",
|
"zucchini",
|
||||||
], { terminalBlock: block });
|
], { terminalBlock: block, allOption: true });
|
||||||
cliLog(val || "No value");
|
cliLog(val || "No value");
|
||||||
|
|
||||||
// Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
|
// Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@bearmetal/pdf-tools",
|
"name": "@bearmetal/pdf-tools",
|
||||||
"version": "1.0.8-l",
|
"version": "1.0.8-n",
|
||||||
"license": "GPL 3.0",
|
"license": "GPL 3.0",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run -A --env-file=.env main.ts",
|
"dev": "deno run -A main.ts",
|
||||||
"compile": "deno compile -o pdf-tools.exe --target x86_64-pc-windows-msvc --include ./asciiart.txt -A ./main.ts",
|
"compile": "deno compile -o pdf-tools.exe --target x86_64-pc-windows-msvc --include ./asciiart.txt -A ./main.ts",
|
||||||
"install": "deno install -fgq --import-map ./deno.json -n checkfields -R ./main.ts",
|
"install": "deno install -fgq --import-map ./deno.json -n checkfields -R ./main.ts",
|
||||||
"debug": "deno run -A --env-file=.env --inspect-wait --watch main.ts"
|
"debug": "deno run -A --env-file=.env --inspect-wait --watch main.ts"
|
||||||
|
50
tools/deleteFields.ts
Normal file
50
tools/deleteFields.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { forceArgs } from "../cli/forceArgs.ts";
|
||||||
|
import { cliPrompt } from "../cli/prompts.ts";
|
||||||
|
import { multiSelectMenuInteractive } from "../cli/selectMenu.ts";
|
||||||
|
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
||||||
|
import type { callback, ITool } from "../types.ts";
|
||||||
|
import { loadPdf, savePdf } from "util/saveLoadPdf.ts";
|
||||||
|
import { log } from "util/logfile.ts";
|
||||||
|
|
||||||
|
export class DeleteFormFields implements ITool {
|
||||||
|
name = "deleteFormFields";
|
||||||
|
description = "delete multiple form fields from a PDF";
|
||||||
|
block?: TerminalBlock;
|
||||||
|
|
||||||
|
async run(pdfPath: string = "") {
|
||||||
|
if (!this.block) this.block = new TerminalBlock();
|
||||||
|
[pdfPath] = await forceArgs([pdfPath], [[
|
||||||
|
"Please provide path to PDF",
|
||||||
|
(d) => d?.endsWith(".pdf"),
|
||||||
|
]], this.block);
|
||||||
|
|
||||||
|
const pdf = await loadPdf(pdfPath);
|
||||||
|
const form = pdf.getForm();
|
||||||
|
const fields = form.getFields();
|
||||||
|
let updatesMade = false;
|
||||||
|
await multiSelectMenuInteractive(
|
||||||
|
`${pdfPath}\nSelect fields to delete:`,
|
||||||
|
fields.map<[string, callback]>((
|
||||||
|
f,
|
||||||
|
) => [f.getName(), () => {
|
||||||
|
while (f.acroField.getWidgets().length) {
|
||||||
|
f.acroField.removeWidget(0);
|
||||||
|
}
|
||||||
|
form.removeField(f);
|
||||||
|
updatesMade = true;
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
if (!updatesMade) return;
|
||||||
|
const path = await cliPrompt(
|
||||||
|
"Save to path (or hit enter to keep current):",
|
||||||
|
this.block,
|
||||||
|
) || pdfPath;
|
||||||
|
await savePdf(pdf, path);
|
||||||
|
}
|
||||||
|
help?: (() => Promise<void> | void) | undefined;
|
||||||
|
done?: (() => Promise<void> | void) | undefined;
|
||||||
|
setBlock(block: TerminalBlock) {
|
||||||
|
this.block = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default new DeleteFormFields();
|
@ -404,7 +404,7 @@ class RenameFields implements ITool {
|
|||||||
if (!this.block) {
|
if (!this.block) {
|
||||||
this.block = new TerminalBlock();
|
this.block = new TerminalBlock();
|
||||||
}
|
}
|
||||||
this.block.setPreserveHistory(true);
|
this.block.setPreserveHistory(false);
|
||||||
|
|
||||||
[pdfPath, pattern, change] = await forceArgs(
|
[pdfPath, pattern, change] = await forceArgs(
|
||||||
[pdfPath, pattern, change],
|
[pdfPath, pattern, change],
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { forceArgs } from "../cli/forceArgs.ts";
|
import { forceArgs } from "../cli/forceArgs.ts";
|
||||||
import { cliAlert } from "../cli/prompts.ts";
|
|
||||||
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
||||||
import { loadPdfForm } from "util/saveLoadPdf.ts";
|
import { loadPdfForm } from "util/saveLoadPdf.ts";
|
||||||
import type { ITool } from "../types.ts";
|
import type { ITool } from "../types.ts";
|
||||||
|
import { InputManager } from "../cli/InputManager.ts";
|
||||||
|
|
||||||
export class ListFormFields implements ITool {
|
export class ListFormFields implements ITool {
|
||||||
name = "listformfields";
|
name = "listformfields";
|
||||||
@ -12,21 +12,48 @@ export class ListFormFields implements ITool {
|
|||||||
if (!this.block) {
|
if (!this.block) {
|
||||||
this.block = new TerminalBlock();
|
this.block = new TerminalBlock();
|
||||||
}
|
}
|
||||||
this.block.setPreserveHistory(true);
|
this.block.setPreserveHistory(false);
|
||||||
[pdfPath] = await forceArgs([pdfPath], [[
|
[pdfPath] = await forceArgs([pdfPath], [[
|
||||||
"Please provide path to PDF:",
|
"Please provide path to PDF:",
|
||||||
(p) => !!p && p.endsWith(".pdf"),
|
(p) => !!p && p.endsWith(".pdf"),
|
||||||
]], this.block);
|
]], this.block);
|
||||||
|
const lines = [pdfPath];
|
||||||
|
let rLines: string[] = [];
|
||||||
|
|
||||||
const form = await loadPdfForm(pdfPath);
|
const form = await loadPdfForm(pdfPath);
|
||||||
const fields = form.getFields();
|
const fields = form.getFields();
|
||||||
const fieldNames = fields.map((f) => f.getName());
|
const fieldNames = fields.map((f) => f.getName());
|
||||||
const lines = [];
|
|
||||||
for (const fieldName of fieldNames) {
|
let offset = 0;
|
||||||
lines.push(fieldName);
|
|
||||||
}
|
const buildRLines = () => {
|
||||||
this.block.setLines(lines, [0, 1]);
|
rLines = fieldNames.slice(offset, this.block!.getRenderHeight());
|
||||||
await cliAlert("", this.block);
|
this.block!.setLines(lines.concat(rLines), [0, 1]);
|
||||||
|
};
|
||||||
|
buildRLines();
|
||||||
|
|
||||||
|
await new Promise<void>((res) => {
|
||||||
|
const im = InputManager.getInstance();
|
||||||
|
const up = () => {
|
||||||
|
if (fieldNames.length < this.block!.getRenderHeight() - 1) return;
|
||||||
|
offset = Math.max(0, offset - 1);
|
||||||
|
buildRLines();
|
||||||
|
};
|
||||||
|
const down = () => {
|
||||||
|
if (fieldNames.length < this.block!.getRenderHeight() - 1) return;
|
||||||
|
offset = Math.min(fieldNames.length, offset + 1);
|
||||||
|
buildRLines();
|
||||||
|
};
|
||||||
|
const enter = () => {
|
||||||
|
res();
|
||||||
|
im.removeEventListener("arrow-up", up);
|
||||||
|
im.removeEventListener("arrow-down", down);
|
||||||
|
im.removeEventListener("enter", enter);
|
||||||
|
};
|
||||||
|
im.addEventListener("arrow-up", up);
|
||||||
|
im.addEventListener("arrow-down", down);
|
||||||
|
im.addEventListener("enter", enter);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setBlock(terminalBlock: TerminalBlock) {
|
setBlock(terminalBlock: TerminalBlock) {
|
||||||
this.block = terminalBlock;
|
this.block = terminalBlock;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user