feat: integrate fix page size functionality into the workflow by extracting core logic to a utility and adding a new workflow node.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { showAlert } from '../ui.js';
|
import { showAlert } from '../ui.js';
|
||||||
import { downloadFile, formatBytes, hexToRgb } from '../utils/helpers.js';
|
import { downloadFile, formatBytes, hexToRgb } from '../utils/helpers.js';
|
||||||
import { PDFDocument as PDFLibDocument, rgb, PageSizes } from 'pdf-lib';
|
import { fixPageSize as fixPageSizeCore } from '../utils/pdf-operations';
|
||||||
import { icons, createIcons } from 'lucide';
|
import { icons, createIcons } from 'lucide';
|
||||||
import { FixPageSizeState } from '@/types';
|
import { FixPageSizeState } from '@/types';
|
||||||
|
|
||||||
@@ -31,7 +31,8 @@ async function updateUI() {
|
|||||||
|
|
||||||
if (pageState.file) {
|
if (pageState.file) {
|
||||||
const fileDiv = document.createElement('div');
|
const fileDiv = document.createElement('div');
|
||||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
fileDiv.className =
|
||||||
|
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||||
|
|
||||||
const infoContainer = document.createElement('div');
|
const infoContainer = document.createElement('div');
|
||||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||||
@@ -66,7 +67,10 @@ async function updateUI() {
|
|||||||
function handleFileSelect(files: FileList | null) {
|
function handleFileSelect(files: FileList | null) {
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
if (
|
||||||
|
file.type === 'application/pdf' ||
|
||||||
|
file.name.toLowerCase().endsWith('.pdf')
|
||||||
|
) {
|
||||||
pageState.file = file;
|
pageState.file = file;
|
||||||
updateUI();
|
updateUI();
|
||||||
}
|
}
|
||||||
@@ -79,10 +83,20 @@ async function fixPageSize() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetSizeKey = (document.getElementById('target-size') as HTMLSelectElement).value;
|
const targetSize = (
|
||||||
const orientation = (document.getElementById('orientation') as HTMLSelectElement).value;
|
document.getElementById('target-size') as HTMLSelectElement
|
||||||
const scalingMode = (document.querySelector('input[name="scaling-mode"]:checked') as HTMLInputElement).value;
|
).value;
|
||||||
const backgroundColor = hexToRgb((document.getElementById('background-color') as HTMLInputElement).value);
|
const orientation = (
|
||||||
|
document.getElementById('orientation') as HTMLSelectElement
|
||||||
|
).value;
|
||||||
|
const scalingMode = (
|
||||||
|
document.querySelector(
|
||||||
|
'input[name="scaling-mode"]:checked'
|
||||||
|
) as HTMLInputElement
|
||||||
|
).value;
|
||||||
|
const backgroundColor = hexToRgb(
|
||||||
|
(document.getElementById('background-color') as HTMLInputElement).value
|
||||||
|
);
|
||||||
|
|
||||||
const loaderModal = document.getElementById('loader-modal');
|
const loaderModal = document.getElementById('loader-modal');
|
||||||
const loaderText = document.getElementById('loader-text');
|
const loaderText = document.getElementById('loader-text');
|
||||||
@@ -90,72 +104,43 @@ async function fixPageSize() {
|
|||||||
if (loaderText) loaderText.textContent = 'Standardizing pages...';
|
if (loaderText) loaderText.textContent = 'Standardizing pages...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let targetWidth, targetHeight;
|
const customWidth =
|
||||||
|
parseFloat(
|
||||||
if (targetSizeKey === 'Custom') {
|
(document.getElementById('custom-width') as HTMLInputElement)?.value
|
||||||
const width = parseFloat((document.getElementById('custom-width') as HTMLInputElement).value);
|
) || 210;
|
||||||
const height = parseFloat((document.getElementById('custom-height') as HTMLInputElement).value);
|
const customHeight =
|
||||||
const units = (document.getElementById('custom-units') as HTMLSelectElement).value;
|
parseFloat(
|
||||||
|
(document.getElementById('custom-height') as HTMLInputElement)?.value
|
||||||
if (units === 'in') {
|
) || 297;
|
||||||
targetWidth = width * 72;
|
const customUnits =
|
||||||
targetHeight = height * 72;
|
(document.getElementById('custom-units') as HTMLSelectElement)?.value ||
|
||||||
} else {
|
'mm';
|
||||||
// mm
|
|
||||||
targetWidth = width * (72 / 25.4);
|
|
||||||
targetHeight = height * (72 / 25.4);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[targetWidth, targetHeight] = PageSizes[targetSizeKey as keyof typeof PageSizes];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orientation === 'landscape' && targetWidth < targetHeight) {
|
|
||||||
[targetWidth, targetHeight] = [targetHeight, targetWidth];
|
|
||||||
} else if (orientation === 'portrait' && targetWidth > targetHeight) {
|
|
||||||
[targetWidth, targetHeight] = [targetHeight, targetWidth];
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await pageState.file.arrayBuffer();
|
const arrayBuffer = await pageState.file.arrayBuffer();
|
||||||
const sourceDoc = await PDFLibDocument.load(arrayBuffer);
|
const pdfBytes = new Uint8Array(arrayBuffer);
|
||||||
const newDoc = await PDFLibDocument.create();
|
|
||||||
|
|
||||||
for (const sourcePage of sourceDoc.getPages()) {
|
const newPdfBytes = await fixPageSizeCore(pdfBytes, {
|
||||||
const { width: sourceWidth, height: sourceHeight } = sourcePage.getSize();
|
targetSize,
|
||||||
const embeddedPage = await newDoc.embedPage(sourcePage);
|
orientation,
|
||||||
|
scalingMode,
|
||||||
const newPage = newDoc.addPage([targetWidth, targetHeight]);
|
backgroundColor,
|
||||||
newPage.drawRectangle({
|
customWidth,
|
||||||
x: 0,
|
customHeight,
|
||||||
y: 0,
|
customUnits,
|
||||||
width: targetWidth,
|
|
||||||
height: targetHeight,
|
|
||||||
color: rgb(backgroundColor.r, backgroundColor.g, backgroundColor.b),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const scaleX = targetWidth / sourceWidth;
|
|
||||||
const scaleY = targetHeight / sourceHeight;
|
|
||||||
const scale = scalingMode === 'fit' ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);
|
|
||||||
|
|
||||||
const scaledWidth = sourceWidth * scale;
|
|
||||||
const scaledHeight = sourceHeight * scale;
|
|
||||||
|
|
||||||
const x = (targetWidth - scaledWidth) / 2;
|
|
||||||
const y = (targetHeight - scaledHeight) / 2;
|
|
||||||
|
|
||||||
newPage.drawPage(embeddedPage, {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width: scaledWidth,
|
|
||||||
height: scaledHeight,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPdfBytes = await newDoc.save();
|
|
||||||
downloadFile(
|
downloadFile(
|
||||||
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
|
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
|
||||||
'standardized.pdf'
|
'standardized.pdf'
|
||||||
);
|
);
|
||||||
showAlert('Success', 'Page sizes standardized successfully!', 'success', () => { resetState(); });
|
showAlert(
|
||||||
|
'Success',
|
||||||
|
'Page sizes standardized successfully!',
|
||||||
|
'success',
|
||||||
|
() => {
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
showAlert('Error', 'An error occurred while standardizing pages.');
|
showAlert('Error', 'An error occurred while standardizing pages.');
|
||||||
@@ -209,7 +194,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const files = e.dataTransfer?.files;
|
const files = e.dataTransfer?.files;
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
const pdfFiles = Array.from(files).filter(function (f) {
|
const pdfFiles = Array.from(files).filter(function (f) {
|
||||||
return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf');
|
return (
|
||||||
|
f.type === 'application/pdf' ||
|
||||||
|
f.name.toLowerCase().endsWith('.pdf')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (pdfFiles.length > 0) {
|
if (pdfFiles.length > 0) {
|
||||||
const dataTransfer = new DataTransfer();
|
const dataTransfer = new DataTransfer();
|
||||||
|
|||||||
@@ -1133,9 +1133,7 @@ function showNodeSettings(node: BaseWorkflowNode) {
|
|||||||
{ label: 'Top Right', value: 'top-right' },
|
{ label: 'Top Right', value: 'top-right' },
|
||||||
],
|
],
|
||||||
orientation: [
|
orientation: [
|
||||||
{ label: 'Vertical', value: 'vertical' },
|
{ label: 'Auto (Keep Original)', value: 'auto' },
|
||||||
{ label: 'Horizontal', value: 'horizontal' },
|
|
||||||
{ label: 'Auto', value: 'auto' },
|
|
||||||
{ label: 'Portrait', value: 'portrait' },
|
{ label: 'Portrait', value: 'portrait' },
|
||||||
{ label: 'Landscape', value: 'landscape' },
|
{ label: 'Landscape', value: 'landscape' },
|
||||||
],
|
],
|
||||||
@@ -1160,6 +1158,23 @@ function showNodeSettings(node: BaseWorkflowNode) {
|
|||||||
{ label: 'Letter', value: 'letter' },
|
{ label: 'Letter', value: 'letter' },
|
||||||
{ label: 'Legal', value: 'legal' },
|
{ label: 'Legal', value: 'legal' },
|
||||||
],
|
],
|
||||||
|
targetSize: [
|
||||||
|
{ label: 'A4', value: 'A4' },
|
||||||
|
{ label: 'Letter', value: 'Letter' },
|
||||||
|
{ label: 'Legal', value: 'Legal' },
|
||||||
|
{ label: 'A3', value: 'A3' },
|
||||||
|
{ label: 'A5', value: 'A5' },
|
||||||
|
{ label: 'Tabloid', value: 'Tabloid' },
|
||||||
|
{ label: 'Custom', value: 'Custom' },
|
||||||
|
],
|
||||||
|
scalingMode: [
|
||||||
|
{ label: 'Fit (keep full page visible)', value: 'fit' },
|
||||||
|
{ label: 'Fill (cover full target page)', value: 'fill' },
|
||||||
|
],
|
||||||
|
customUnits: [
|
||||||
|
{ label: 'Millimeters (mm)', value: 'mm' },
|
||||||
|
{ label: 'Inches (in)', value: 'in' },
|
||||||
|
],
|
||||||
numberFormat: [
|
numberFormat: [
|
||||||
{ label: 'Simple (1, 2, 3)', value: 'simple' },
|
{ label: 'Simple (1, 2, 3)', value: 'simple' },
|
||||||
{ label: 'Page X of Y', value: 'page_x_of_y' },
|
{ label: 'Page X of Y', value: 'page_x_of_y' },
|
||||||
@@ -1294,6 +1309,9 @@ function showNodeSettings(node: BaseWorkflowNode) {
|
|||||||
text: ['text'],
|
text: ['text'],
|
||||||
area: ['x0', 'y0', 'x1', 'y1'],
|
area: ['x0', 'y0', 'x1', 'y1'],
|
||||||
},
|
},
|
||||||
|
targetSize: {
|
||||||
|
Custom: ['customWidth', 'customHeight', 'customUnits'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const controlWrappers: Record<string, HTMLElement> = {};
|
const controlWrappers: Record<string, HTMLElement> = {};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PDFDocument, degrees, rgb, StandardFonts } from 'pdf-lib';
|
import { PDFDocument, degrees, rgb, StandardFonts, PageSizes } from 'pdf-lib';
|
||||||
|
|
||||||
export async function mergePdfs(
|
export async function mergePdfs(
|
||||||
pdfBytesList: Uint8Array[]
|
pdfBytesList: Uint8Array[]
|
||||||
@@ -438,3 +438,87 @@ export async function addPageNumbers(
|
|||||||
|
|
||||||
return new Uint8Array(await pdfDoc.save());
|
return new Uint8Array(await pdfDoc.save());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FixPageSizeOptions {
|
||||||
|
targetSize: string;
|
||||||
|
orientation: string;
|
||||||
|
scalingMode: string;
|
||||||
|
backgroundColor: { r: number; g: number; b: number };
|
||||||
|
customWidth?: number;
|
||||||
|
customHeight?: number;
|
||||||
|
customUnits?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fixPageSize(
|
||||||
|
pdfBytes: Uint8Array,
|
||||||
|
options: FixPageSizeOptions
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
let targetWidth: number;
|
||||||
|
let targetHeight: number;
|
||||||
|
|
||||||
|
if (options.targetSize.toLowerCase() === 'custom') {
|
||||||
|
const w = options.customWidth ?? 210;
|
||||||
|
const h = options.customHeight ?? 297;
|
||||||
|
const units = (options.customUnits ?? 'mm').toLowerCase();
|
||||||
|
if (units === 'in') {
|
||||||
|
targetWidth = w * 72;
|
||||||
|
targetHeight = h * 72;
|
||||||
|
} else {
|
||||||
|
targetWidth = w * (72 / 25.4);
|
||||||
|
targetHeight = h * (72 / 25.4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const selected =
|
||||||
|
PageSizes[options.targetSize as keyof typeof PageSizes] || PageSizes.A4;
|
||||||
|
targetWidth = selected[0];
|
||||||
|
targetHeight = selected[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const orientation = options.orientation.toLowerCase();
|
||||||
|
if (orientation === 'landscape' && targetWidth < targetHeight) {
|
||||||
|
[targetWidth, targetHeight] = [targetHeight, targetWidth];
|
||||||
|
} else if (orientation === 'portrait' && targetWidth > targetHeight) {
|
||||||
|
[targetWidth, targetHeight] = [targetHeight, targetWidth];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceDoc = await PDFDocument.load(pdfBytes);
|
||||||
|
const outputDoc = await PDFDocument.create();
|
||||||
|
|
||||||
|
for (const sourcePage of sourceDoc.getPages()) {
|
||||||
|
const { width: sourceWidth, height: sourceHeight } = sourcePage.getSize();
|
||||||
|
const embeddedPage = await outputDoc.embedPage(sourcePage);
|
||||||
|
|
||||||
|
const outputPage = outputDoc.addPage([targetWidth, targetHeight]);
|
||||||
|
outputPage.drawRectangle({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: targetWidth,
|
||||||
|
height: targetHeight,
|
||||||
|
color: rgb(
|
||||||
|
options.backgroundColor.r,
|
||||||
|
options.backgroundColor.g,
|
||||||
|
options.backgroundColor.b
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const scaleX = targetWidth / sourceWidth;
|
||||||
|
const scaleY = targetHeight / sourceHeight;
|
||||||
|
const useFill = options.scalingMode.toLowerCase() === 'fill';
|
||||||
|
const scale = useFill ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
|
||||||
|
|
||||||
|
const scaledWidth = sourceWidth * scale;
|
||||||
|
const scaledHeight = sourceHeight * scale;
|
||||||
|
|
||||||
|
const x = (targetWidth - scaledWidth) / 2;
|
||||||
|
const y = (targetHeight - scaledHeight) / 2;
|
||||||
|
|
||||||
|
outputPage.drawPage(embeddedPage, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: scaledWidth,
|
||||||
|
height: scaledHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(await outputDoc.save());
|
||||||
|
}
|
||||||
|
|||||||
104
src/js/workflow/nodes/fix-page-size-node.ts
Normal file
104
src/js/workflow/nodes/fix-page-size-node.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { ClassicPreset } from 'rete';
|
||||||
|
import { BaseWorkflowNode } from './base-node';
|
||||||
|
import { pdfSocket } from '../sockets';
|
||||||
|
import type { SocketData } from '../types';
|
||||||
|
import { requirePdfInput, processBatch } from '../types';
|
||||||
|
import { fixPageSize } from '../../utils/pdf-operations';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import { hexToRgb } from '../../utils/helpers.js';
|
||||||
|
|
||||||
|
export class FixPageSizeNode extends BaseWorkflowNode {
|
||||||
|
readonly category = 'Organize & Manage' as const;
|
||||||
|
readonly icon = 'ph-frame-corners';
|
||||||
|
readonly description = 'Standardize all pages to a target size';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Fix Page Size');
|
||||||
|
this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF'));
|
||||||
|
this.addOutput(
|
||||||
|
'pdf',
|
||||||
|
new ClassicPreset.Output(pdfSocket, 'Standardized PDF')
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'targetSize',
|
||||||
|
new ClassicPreset.InputControl('text', { initial: 'A4' })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'orientation',
|
||||||
|
new ClassicPreset.InputControl('text', { initial: 'auto' })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'scalingMode',
|
||||||
|
new ClassicPreset.InputControl('text', { initial: 'fit' })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'backgroundColor',
|
||||||
|
new ClassicPreset.InputControl('text', { initial: '#ffffff' })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'customWidth',
|
||||||
|
new ClassicPreset.InputControl('number', { initial: 210 })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'customHeight',
|
||||||
|
new ClassicPreset.InputControl('number', { initial: 297 })
|
||||||
|
);
|
||||||
|
this.addControl(
|
||||||
|
'customUnits',
|
||||||
|
new ClassicPreset.InputControl('text', { initial: 'mm' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async data(
|
||||||
|
inputs: Record<string, SocketData[]>
|
||||||
|
): Promise<Record<string, SocketData>> {
|
||||||
|
const pdfInputs = requirePdfInput(inputs, 'Fix Page Size');
|
||||||
|
|
||||||
|
const getText = (key: string, fallback: string) => {
|
||||||
|
const ctrl = this.controls[key] as
|
||||||
|
| ClassicPreset.InputControl<'text'>
|
||||||
|
| undefined;
|
||||||
|
return (ctrl?.value || fallback).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNum = (key: string, fallback: number) => {
|
||||||
|
const ctrl = this.controls[key] as
|
||||||
|
| ClassicPreset.InputControl<'number'>
|
||||||
|
| undefined;
|
||||||
|
const value = ctrl?.value;
|
||||||
|
return Number.isFinite(value) ? (value as number) : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetSize = getText('targetSize', 'A4');
|
||||||
|
const orientation = getText('orientation', 'auto');
|
||||||
|
const scalingMode = getText('scalingMode', 'fit');
|
||||||
|
const backgroundHex = getText('backgroundColor', '#ffffff');
|
||||||
|
const customWidth = Math.max(1, getNum('customWidth', 210));
|
||||||
|
const customHeight = Math.max(1, getNum('customHeight', 297));
|
||||||
|
const customUnits = getText('customUnits', 'mm');
|
||||||
|
const backgroundColor = hexToRgb(backgroundHex);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pdf: await processBatch(pdfInputs, async (input) => {
|
||||||
|
const resultBytes = await fixPageSize(input.bytes, {
|
||||||
|
targetSize,
|
||||||
|
orientation,
|
||||||
|
scalingMode,
|
||||||
|
backgroundColor,
|
||||||
|
customWidth,
|
||||||
|
customHeight,
|
||||||
|
customUnits,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultDoc = await PDFDocument.load(resultBytes);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'pdf',
|
||||||
|
document: resultDoc,
|
||||||
|
bytes: resultBytes,
|
||||||
|
filename: input.filename.replace(/\.pdf$/i, '_standardized.pdf'),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { ReversePagesNode } from './reverse-pages-node';
|
|||||||
import { AddBlankPageNode } from './add-blank-page-node';
|
import { AddBlankPageNode } from './add-blank-page-node';
|
||||||
import { DividePagesNode } from './divide-pages-node';
|
import { DividePagesNode } from './divide-pages-node';
|
||||||
import { NUpNode } from './n-up-node';
|
import { NUpNode } from './n-up-node';
|
||||||
|
import { FixPageSizeNode } from './fix-page-size-node';
|
||||||
import { CombineSinglePageNode } from './combine-single-page-node';
|
import { CombineSinglePageNode } from './combine-single-page-node';
|
||||||
import { CropNode } from './crop-node';
|
import { CropNode } from './crop-node';
|
||||||
import { GreyscaleNode } from './greyscale-node';
|
import { GreyscaleNode } from './greyscale-node';
|
||||||
@@ -298,6 +299,13 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
|
|||||||
description: 'Arrange multiple pages per sheet',
|
description: 'Arrange multiple pages per sheet',
|
||||||
factory: () => new NUpNode(),
|
factory: () => new NUpNode(),
|
||||||
},
|
},
|
||||||
|
FixPageSizeNode: {
|
||||||
|
label: 'Fix Page Size',
|
||||||
|
category: 'Organize & Manage',
|
||||||
|
icon: 'ph-frame-corners',
|
||||||
|
description: 'Standardize all pages to a target size',
|
||||||
|
factory: () => new FixPageSizeNode(),
|
||||||
|
},
|
||||||
CombineSinglePageNode: {
|
CombineSinglePageNode: {
|
||||||
label: 'Combine to Single Page',
|
label: 'Combine to Single Page',
|
||||||
category: 'Organize & Manage',
|
category: 'Organize & Manage',
|
||||||
|
|||||||
Reference in New Issue
Block a user